This commit is contained in:
2026-02-07 08:28:56 +03:00
parent eff857a55e
commit 451ddd9ae0
30 changed files with 3993 additions and 1233 deletions
+12
View File
@@ -21,15 +21,21 @@ RC_ICONS = ico\app_icon.ico
INCLUDEPATH += $$PWD INCLUDEPATH += $$PWD
SOURCES += \ SOURCES += \
commandprocessor.cpp \
emoteprovider.cpp \ emoteprovider.cpp \
fcolorsetting.cpp \ fcolorsetting.cpp \
fcreatechat.cpp \ fcreatechat.cpp \
fcreatenotify.cpp \ fcreatenotify.cpp \
ffontsetting.cpp \ ffontsetting.cpp \
filemanager.cpp \
fsettingsws.cpp \ fsettingsws.cpp \
fsinglegrid.cpp \ fsinglegrid.cpp \
logmanager.cpp \
main.cpp \ main.cpp \
mediafilemanager.cpp \
neuralnetworkmanager.cpp \ neuralnetworkmanager.cpp \
randommanager.cpp \
randomresponses.cpp \
soundmanager.cpp \ soundmanager.cpp \
tauth.cpp \ tauth.cpp \
ttw_api.cpp \ ttw_api.cpp \
@@ -46,15 +52,21 @@ SOURCES += \
HEADERS += \ HEADERS += \
badge.h \ badge.h \
commandprocessor.h \
emoteprovider.h \ emoteprovider.h \
fcolorsetting.h \ fcolorsetting.h \
fcreatechat.h \ fcreatechat.h \
fcreatenotify.h \ fcreatenotify.h \
ffontsetting.h \ ffontsetting.h \
filemanager.h \
fsettingsws.h \ fsettingsws.h \
fsinglegrid.h \ fsinglegrid.h \
logmanager.h \
mediafilemanager.h \
miniaudio.h \ miniaudio.h \
neuralnetworkmanager.h \ neuralnetworkmanager.h \
randommanager.h \
randomresponses.h \
soundmanager.h \ soundmanager.h \
tauth.h \ tauth.h \
timerinfo.h \ timerinfo.h \
+476
View File
@@ -0,0 +1,476 @@
#include "commandprocessor.h"
#include <QRegularExpression>
#include <QRandomGenerator>
#include <QFile>
#include <QTextStream>
#include <QEventLoop>
#include <QTimer>
CommandProcessor::CommandProcessor(QObject *parent)
: QObject(parent)
{
}
void CommandProcessor::setContext(const Context& context)
{
m_context = context;
}
QString CommandProcessor::generateResponse(QString userIndex, const QString &command, const QString &message)
{
qDebug() << "generateResponse: userIndex =" << userIndex << "command =" << command;
// Сначала пробуем найти пользователя по displayName
QString username = "";
if (m_context.userManager) {
User* user = m_context.userManager->findUser(userIndex);
if (user) {
username = user->displayName;
qDebug() << "Найден пользователь:" << username;
} else {
qDebug() << "Пользователь не найден в UserManager по displayName:" << userIndex;
// Попробуем найти по ID
user = m_context.userManager->findUserById(userIndex);
if (user) {
username = user->displayName;
qDebug() << "Найден пользователь по ID:" << username;
}
}
}
if (username.isEmpty()) {
// Если не нашли в UserManager, используем переданное имя
username = userIndex;
qDebug() << "Используем переданное имя:" << username;
}
Command cmd = findCommand(command);
if (cmd.command.isEmpty()) {
qDebug() << "Команда не найдена:" << command;
return QString("Команда '%1' не найдена").arg(command);
}
qDebug() << "Найдена команда:" << cmd.command << "ответ:" << cmd.response;
QString fullCommand = command + (message.isEmpty() ? "" : " " + message);
QString result = processCommand(username, fullCommand, cmd.response);
qDebug() << "Итоговый результат:" << result;
return result;
}
void CommandProcessor::addCommand(const QString &command, const QString &response)
{
m_commands.append({command, response});
}
void CommandProcessor::addCommands(const QVector<Command> &commands)
{
m_commands.append(commands);
}
void CommandProcessor::clearCommands()
{
m_commands.clear();
}
CommandProcessor::Command CommandProcessor::findCommand(const QString &commandName) const
{
for (const Command &cmd : m_commands) {
if (cmd.command.compare(commandName, Qt::CaseInsensitive) == 0) {
return cmd;
}
}
return Command();
}
QString CommandProcessor::getUsernameByIndex(QString userIndex) const
{
if (!m_context.userManager) {
return QString();
}
const User* user = m_context.userManager->getUserByIndex(userIndex);
return user ? user->displayName : QString();
}
QString CommandProcessor::processCommand(const QString &sender, const QString &fullCommand, const QString &rawResponse)
{
QString response = rawResponse;
QString parameters = extractParameters(fullCommand);
response = parseStatic(response, sender, parameters);
// Рекурсивная обработка случайных групп (до 5 уровней вложенности)
for (int i = 0; i < 5; i++) {
QString before = response;
response = parseRandomGroups(response);
// Если после обработки строка не изменилась, значит групп больше нет
if (before == response) {
break;
}
}
response = parseRandomNumbers(response); // Затем обрабатываем случайные числа ВНУТРИ них
response = parseSounds(response);
response = parseTextFiles(response);
response = parseBan(response, sender);
response = parseAPI(response, sender);
if (response.contains("[AI]", Qt::CaseInsensitive)) {
response = parseAI(response, parameters);
}
return response;
}
QString CommandProcessor::extractParameters(const QString &fullCommand)
{
int spacePos = fullCommand.indexOf(' ');
if (spacePos != -1) {
return fullCommand.mid(spacePos + 1).trimmed();
}
return QString();
}
QString CommandProcessor::parseStatic(const QString &response, const QString &sender, const QString &parameters)
{
QString res = response;
res = res.replace("[USERNAME]", "@" + sender);
res = res.replace("[TO]", parameters);
if (res.contains("[RANDOMUSER]")) {
QString randomUserName;
if (parameters.contains('@')) {
randomUserName = parameters;
} else if (m_context.userManager) {
const User* randomUser = m_context.userManager->getRandomUser();
if (randomUser) {
randomUserName = "@" + randomUser->displayName;
} else {
randomUserName = "@viewer";
}
} else {
randomUserName = "@viewer";
}
res = res.replace("[RANDOMUSER]", randomUserName);
}
return res;
}
QString CommandProcessor::parseRandomNumbers(const QString &response)
{
QString result = response;
QRegularExpression regex("\\[\\[([^\\]]+)\\]\\]");
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
qDebug() << "parseRandomNumbers: исходная строка:" << response;
qDebug() << "Найдено совпадений:" << matches.hasNext();
while (matches.hasNext()) {
QRegularExpressionMatch match = matches.next();
QString rangeName = match.captured(1);
qDebug() << "Найден диапазон:" << rangeName;
if (m_context.randomManager) {
int randomNumber = m_context.randomManager->getRandomValue(rangeName);
qDebug() << "Получено случайное число:" << randomNumber << "для диапазона" << rangeName;
result.replace("[[" + rangeName + "]]", QString::number(randomNumber));
} else {
qDebug() << "RandomManager не инициализирован!";
int fallbackNumber = QRandomGenerator::global()->bounded(1, 101);
result.replace("[[" + rangeName + "]]", QString::number(fallbackNumber));
}
}
qDebug() << "Результат после замены:" << result;
return result;
}
QString CommandProcessor::parseSounds(const QString &response)
{
QString result = response;
QRegularExpression regex("\\|\\|([^\\|]+)\\|\\|");
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
while (matches.hasNext()) {
QRegularExpressionMatch match = matches.next();
QString soundCommand = match.captured(1);
QString fn = m_context.mediaFileManager->getFilePathByName(soundCommand);
if (m_context.soundManager) {
m_context.soundManager->loadSound(SoundManager::SoundChannel::Channel1, fn);
m_context.soundManager->playSound(SoundManager::SoundChannel::Channel1);
}
result.replace("||" + soundCommand + "||", "");
}
return result;
}
QString CommandProcessor::parseTextFiles(const QString &response)
{
QString result = response;
QRegularExpression regex("\\|\\(([^\\)]+)\\|\\(");
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
while (matches.hasNext()) {
QRegularExpressionMatch match = matches.next();
QString fileName = match.captured(1);
QString content = getTextFileContent(fileName);
if (!content.isEmpty()) {
result.replace("|(" + fileName + "|(", content);
}
}
return result;
}
QString CommandProcessor::parseRandomGroups(const QString &response)
{
QString result = response;
QRegularExpression regex("\\{\\{([^\\}]+)\\}\\}");
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
while (matches.hasNext()) {
QRegularExpressionMatch match = matches.next();
QString groupName = match.captured(1);
QString randomResponse = getRandomResponseFromGroup(groupName);
if (!randomResponse.isEmpty()) {
result.replace("{{" + groupName + "}}", randomResponse);
}
}
return result;
}
QString CommandProcessor::parseBan(const QString &response, const QString &sender)
{
QString result = response;
QRegularExpression regex("\\[BAN(\\d*)\\]");
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
while (matches.hasNext()) {
QRegularExpressionMatch match = matches.next();
QString banTimeStr = match.captured(1);
int banSeconds = banTimeStr.isEmpty() ? 0 : banTimeStr.toInt();
if (m_context.twitchAPI) {
if (m_context.userManager) {
User* user = m_context.userManager->findUser(sender);
qDebug() << user->displayName;
if (user && !user->id.isEmpty()) {
if (banSeconds > 0) {
m_context.twitchAPI->banUserTime(user->id, banSeconds);
}
}
}
}
result.replace("[BAN" + banTimeStr + "]", "");
}
return result;
}
QString CommandProcessor::parseAPI(const QString &response, const QString &sender)
{
QString result = response;
if (result.contains("[FOLLOW]", Qt::CaseInsensitive)) {
if (m_context.userManager && m_context.twitchAPI) {
User* user = m_context.userManager->findUser(sender);
if (user) {
if (user->id.isEmpty() && !user->login.isEmpty()) {
User fullUser = m_context.twitchAPI->getUserByLogin(user->login);
user->id = fullUser.id;
}
if (!user->id.isEmpty()) {
QDate followDate = m_context.twitchAPI->getFollow(user->id);
if (followDate.isValid()) {
QString follow = getDateDifferenceString(followDate);
result.replace("[FOLLOW]", follow, Qt::CaseInsensitive);
}
}
}
}
if (result.contains("[FOLLOW]", Qt::CaseInsensitive)) {
result.replace("[FOLLOW]", "неизвестно", Qt::CaseInsensitive);
}
}
if (result.contains("[AGE]", Qt::CaseInsensitive)) {
if (m_context.userManager && m_context.twitchAPI) {
User* user = m_context.userManager->findUser(sender);
if (user && !user->login.isEmpty()) {
User fullUser = m_context.twitchAPI->getUserByLogin(user->login);
if (fullUser.createdAt.isValid()) {
QString age = getDateDifferenceString(fullUser.createdAt);
result.replace("[AGE]", age, Qt::CaseInsensitive);
}
}
}
if (result.contains("[AGE]", Qt::CaseInsensitive)) {
result.replace("[AGE]", "неизвестно", Qt::CaseInsensitive);
}
}
if (result.contains("[STAT]", Qt::CaseInsensitive)) {
if (m_context.twitchAPI && !m_context.channel.isEmpty()) {
int avgViewers = 0, maxViewers = 0, hoursWatched = 0, followers = 0, followersTotal = 0;
m_context.twitchAPI->getTTWStat(m_context.channel, avgViewers, maxViewers,
hoursWatched, followers, followersTotal);
QString stat = QString("Средний онлайн: %1; Максимальный онлайн: %2; "
"Часов просмотра: %3; Подписчиков за месяц: %4; "
"Всего подписчиков: %5")
.arg(avgViewers).arg(maxViewers).arg(hoursWatched)
.arg(followers).arg(followersTotal);
result.replace("[STAT]", stat, Qt::CaseInsensitive);
} else {
result.replace("[STAT]", "Статистика недоступна", Qt::CaseInsensitive);
}
}
return result;
}
QString CommandProcessor::parseAI(const QString &response, const QString &question)
{
QString res = response;
if (!m_context.neuralManager) {
return res.replace("[AI]", "Нейросеть недоступна", Qt::CaseInsensitive);
}
if (question.isEmpty()) {
return res.replace("[AI]", "Ошибка: не указан вопрос для нейросети", Qt::CaseInsensitive);
}
QString aiResponse;
bool responseReceived = false;
bool errorOccurred = false;
QString errorMessage;
QEventLoop eventLoop;
auto conn1 = connect(m_context.neuralManager, &NeuralNetworkManager::responseReceived,
[&](const QString &response1) {
aiResponse = response1;
responseReceived = true;
eventLoop.quit();
});
auto conn2 = connect(m_context.neuralManager, &NeuralNetworkManager::errorOccurred,
[&](const QString &error) {
errorMessage = error;
errorOccurred = true;
eventLoop.quit();
});
m_context.neuralManager->sendMessage(question, NeuralNetworkManager::DeepSeek);
QTimer::singleShot(60000, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
disconnect(conn1);
disconnect(conn2);
if (errorOccurred) {
return res.replace("[AI]",
QString("Ошибка нейросети: %1").arg(errorMessage),
Qt::CaseInsensitive);
}
if (!responseReceived) {
return res.replace("[AI]",
"Таймаут при ожидании ответа от нейросети",
Qt::CaseInsensitive);
}
return res.replace("[AI]", aiResponse, Qt::CaseInsensitive);
}
QString CommandProcessor::getRandomResponseFromGroup(const QString &groupName)
{
if (m_context.randomResponses) {
return m_context.randomResponses->getResponse(groupName);
}
return QString("Ответ из группы: " + groupName);
}
QString CommandProcessor::getTextFileContent(const QString &fileName)
{
QFile file(fileName);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream stream(&file);
stream.setCodec("UTF-8");
QString content = stream.readAll();
file.close();
return content;
}
return QString();
}
QString CommandProcessor::getDateDifferenceString(const QDate &inputDate)
{
if (!inputDate.isValid() || inputDate.year() < 2000) {
return "неизвестно";
}
QDate currentDate = QDate::currentDate();
if (currentDate < inputDate) {
return "в будущем";
}
int years = inputDate.daysTo(currentDate) / 365;
QDate tempDate = inputDate.addYears(years);
int months = 0;
while (tempDate.addMonths(1) <= currentDate) {
months++;
tempDate = tempDate.addMonths(1);
}
int days = tempDate.daysTo(currentDate);
if (days >= 30) {
months += days / 30;
days = days % 30;
}
QString result;
if (years > 0) {
result += QString("%1 %2 ").arg(years).arg(getPeriodEnding(years, 0));
}
if (months > 0) {
result += QString("%1 %2 ").arg(months).arg(getPeriodEnding(months, 1));
}
if (days > 0 || result.isEmpty()) {
result += QString("%1 %2").arg(days).arg(getPeriodEnding(days, 2));
}
return result.trimmed();
}
QString CommandProcessor::getPeriodEnding(int n, int r)
{
static const QVector<QVector<QString>> endings = {
{"год", "года", "лет"},
{"месяц", "месяца", "месяцев"},
{"день", "дня", "дней"}
};
if (r < 0 || r >= endings.size()) return "";
if (n % 10 == 1 && n % 100 != 11) {
return endings[r][0];
} else if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) {
return endings[r][1];
} else {
return endings[r][2];
}
}
+74
View File
@@ -0,0 +1,74 @@
#ifndef COMMANDPROCESSOR_H
#define COMMANDPROCESSOR_H
#include <QObject>
#include <QString>
#include <QVector>
#include <QMap>
#include <QDate>
#include "mediafilemanager.h"
#include "user_manager.h"
#include "ttw_api.h"
#include "soundmanager.h"
#include "neuralnetworkmanager.h"
#include "randommanager.h"
#include "randomresponses.h"
class CommandProcessor : public QObject
{
Q_OBJECT
public:
struct Command {
QString command;
QString response;
};
struct Context {
UserManager* userManager = nullptr;
TTwAPI* twitchAPI = nullptr;
SoundManager* soundManager = nullptr;
NeuralNetworkManager* neuralManager = nullptr;
RandomManager* randomManager = nullptr;
RandomResponses* randomResponses = nullptr;
MediaFileManager* mediaFileManager = nullptr;
QString channel;
int notifyVolume = 50;
};
explicit CommandProcessor(QObject *parent = nullptr);
void setContext(const Context& context);
QString generateResponse(QString userIndex, const QString &command, const QString &message = "");
void addCommand(const QString &command, const QString &response);
void addCommands(const QVector<Command> &commands);
void clearCommands();
Command findCommand(const QString &commandName) const;
private:
Context m_context;
QVector<Command> m_commands;
QString processCommand(const QString &sender, const QString &fullCommand, const QString &rawResponse);
QString parseStatic(const QString &response, const QString &sender, const QString &parameters);
QString parseRandomNumbers(const QString &response);
QString parseSounds(const QString &response);
QString parseTextFiles(const QString &response);
QString parseRandomGroups(const QString &response);
QString parseBan(const QString &response, const QString &sender);
QString parseAPI(const QString &response, const QString &sender);
QString parseAI(const QString &response, const QString &question);
QString extractParameters(const QString &fullCommand);
QString getUsernameByIndex(QString userIndex) const;
QString getDateDifferenceString(const QDate &inputDate);
QString getPeriodEnding(int n, int r);
QString getRandomResponseFromGroup(const QString &groupName);
QString getTextFileContent(const QString &fileName);
};
#endif // COMMANDPROCESSOR_H
+1 -1
View File
@@ -19,7 +19,7 @@ void EmoteProvider::setLogCallback(LogCallback callback) {
m_logCallback = callback; m_logCallback = callback;
} }
void EmoteProvider::log(const QString &method, const QString &message, LogLevel level) { void EmoteProvider::log(const QString &method, const QString &message, LogLevelemt level) {
if (m_logCallback) { if (m_logCallback) {
m_logCallback(metaObject()->className(), method, message, level); m_logCallback(metaObject()->className(), method, message, level);
} }
+6 -4
View File
@@ -9,16 +9,18 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <functional> #include <functional>
enum LogLevel { enum LogLevelemt {
LOG_INFO = 0, LOG_INFO = 0,
LOG_WARNING = 1, LOG_WARNING = 1,
LOG_ERROR = 2 LOG_ERROR = 2,
LOG_DEBUG = 3
}; };
using LogCallback = std::function<void(const QString& module, using LogCallback = std::function<void(const QString& module,
const QString& method, const QString& method,
const QString& message, const QString& message,
LogLevel level)>; LogLevelemt level)>;
struct BTTVEmote { struct BTTVEmote {
QString id; QString id;
@@ -50,7 +52,7 @@ signals:
void emotesLoaded(); void emotesLoaded();
protected: protected:
void log(const QString &method, const QString &message, LogLevel level); void log(const QString &method, const QString &message, LogLevelemt level);
virtual QString getBaseUrl() const = 0; virtual QString getBaseUrl() const = 0;
virtual void parseGlobalResponse(const QByteArray &data) = 0; virtual void parseGlobalResponse(const QByteArray &data) = 0;
virtual void parseCustomResponse(const QByteArray &data, const QString &userId) = 0; virtual void parseCustomResponse(const QByteArray &data, const QString &userId) = 0;
+170 -68
View File
@@ -1,4 +1,5 @@
#include "fcreatenotify.h" #include "fcreatenotify.h"
#include "udatabase.h"
#include "ui_fcreatenotify.h" #include "ui_fcreatenotify.h"
#include <QFileDialog> #include <QFileDialog>
#include <QDebug> #include <QDebug>
@@ -7,15 +8,19 @@
#include <QCloseEvent> #include <QCloseEvent>
#include <QDesktopServices> #include <QDesktopServices>
#include <QApplication> #include <QApplication>
#include "filemanager.h"
FCreateNotify::FCreateNotify(QWidget *parent) : FCreateNotify::FCreateNotify(uDataBase *database, QWidget *parent) :
QDialog(parent), QDialog(parent),
ui(new Ui::FCreateNotify), ui(new Ui::FCreateNotify),
m_server(nullptr), m_isEditMode(false),
m_notificationCounter(0) m_database(database),
m_existingServerName(""),
m_server(nullptr), // Добавьте
m_notificationCounter(0) // Добавьте
{ {
ui->setupUi(this); ui->setupUi(this);
FileManager::instance().initializeFolderStructure();
// Создаем структуру папок для статических файлов // Создаем структуру папок для статических файлов
QDir appDir(QApplication::applicationDirPath()); QDir appDir(QApplication::applicationDirPath());
appDir.mkpath("sounds"); appDir.mkpath("sounds");
@@ -27,8 +32,7 @@ FCreateNotify::FCreateNotify(QWidget *parent) :
"donation", "follow", "subscription", "raid", "donation", "follow", "subscription", "raid",
"bits", "host", "merch", "goal", "poll", "prediction" "bits", "host", "merch", "goal", "poll", "prediction"
}; };
ui->cbEvent->addItems(events);
ui->cbEvent->setCurrentIndex(0);
// Устанавливаем значения по умолчанию // Устанавливаем значения по умолчанию
ui->edtHeader->setText("Тестовый пользователь"); ui->edtHeader->setText("Тестовый пользователь");
ui->edtMessage->setText("Это тестовое уведомление!"); ui->edtMessage->setText("Это тестовое уведомление!");
@@ -40,7 +44,7 @@ FCreateNotify::FCreateNotify(QWidget *parent) :
// Информация для пользователя // Информация для пользователя
ui->btnTest->setToolTip("Создать тестовое уведомление и открыть браузер"); ui->btnTest->setToolTip("Создать тестовое уведомление и открыть браузер");
ui->btnAdd->setToolTip("Добавить уведомление для отображения"); ui->btnAdd->setToolTip("Добавить уведомление для отображения");
m_server = new HttpServer(nullptr);
} }
FCreateNotify::~FCreateNotify() FCreateNotify::~FCreateNotify()
@@ -101,25 +105,7 @@ void FCreateNotify::on_btnTest_clicked()
createNotification(true); createNotification(true);
} }
void FCreateNotify::on_btnAdd_clicked()
{
// Создаем сервер с текущими настройками
createServer();
if (!m_server) {
QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер");
return;
}
// Получаем название сервера
QString name = QString("Уведомления (порт %1)").arg(m_server->port());
// Отправляем сигнал с созданным сервером
emit serverCreated(m_server, name);
// Закрываем окно
accept();
}
void FCreateNotify::createNotification(bool isTest) void FCreateNotify::createNotification(bool isTest)
{ {
@@ -142,39 +128,24 @@ void FCreateNotify::createNotification(bool isTest)
QString("Тестовое сообщение #%1").arg(m_notificationCounter) : QString("Тестовое сообщение #%1").arg(m_notificationCounter) :
ui->edtMessage->text(); ui->edtMessage->text();
// Добавляем тип события
notif.content = QString("[%1] %2")
.arg(ui->cbEvent->currentText())
.arg(notif.content);
// Картинка // Картинка
QString imgPath = ui->edtFileImg->text(); QString imgPath = ui->edtFileImg->text();
if (!imgPath.isEmpty() && QFile::exists(imgPath)) { if (!imgPath.isEmpty() && QFile::exists(imgPath)) {
QFileInfo imgInfo(imgPath); QString newFileName;
QString destFileName = imgInfo.fileName(); if (FileManager::instance().copyToUserData(imgPath, FileManager::WebServerImages,
QString destPath = getAbsolutePath("imgs/" + destFileName); FileManager::CopyWithNewName, &newFileName)) {
notif.url = FileManager::instance().getWebPath(FileManager::WebServerImages, newFileName);
// Копируем, если файл еще не существует
if (!QFile::exists(destPath)) {
QFile::copy(imgPath, destPath);
} }
notif.url = "/imgs/" + destFileName;
} }
// Звук // Звук
QString soundPath = ui->edtFileSong->text(); QString soundPath = ui->edtFileSong->text();
if (!soundPath.isEmpty() && QFile::exists(soundPath)) { if (!soundPath.isEmpty() && QFile::exists(soundPath)) {
QFileInfo soundInfo(soundPath); QString newFileName;
QString destFileName = soundInfo.fileName(); if (FileManager::instance().copyToUserData(soundPath, FileManager::WebServerSounds,
QString destPath = getAbsolutePath("sounds/" + destFileName); FileManager::CopyWithNewName, &newFileName)) {
notif.soundURL = FileManager::instance().getWebPath(FileManager::WebServerSounds, newFileName);
// Копируем, если файл еще не существует
if (!QFile::exists(destPath)) {
QFile::copy(soundPath, destPath);
} }
notif.soundURL = "/sounds/" + destFileName;
} }
// ПОЛУЧАЕМ НАСТРОЙКИ ИЗ ВИДЖЕТОВ // ПОЛУЧАЕМ НАСТРОЙКИ ИЗ ВИДЖЕТОВ
@@ -277,26 +248,6 @@ void FCreateNotify::createNotification(bool isTest)
} }
} }
void FCreateNotify::copyFileToAppDir(const QString &sourcePath, const QString &destSubDir)
{
QFileInfo sourceInfo(sourcePath);
QString destPath = getAbsolutePath(destSubDir + "/" + sourceInfo.fileName());
// Копируем файл, если он еще не существует или изменился
if (!QFile::exists(destPath) ||
QFileInfo(sourcePath).lastModified() > QFileInfo(destPath).lastModified()) {
QFile::remove(destPath); // Удаляем старую версию
if (QFile::copy(sourcePath, destPath)) {
} else {
}
}
}
QString FCreateNotify::getAbsolutePath(const QString &relativePath)
{
return QApplication::applicationDirPath() + "/" + relativePath;
}
void FCreateNotify::on_btnOpenImg_clicked() void FCreateNotify::on_btnOpenImg_clicked()
{ {
QString fileName = QFileDialog::getOpenFileName(this, QString fileName = QFileDialog::getOpenFileName(this,
@@ -336,3 +287,154 @@ void FCreateNotify::onServerStarted(bool success)
QMessageBox::warning(this, "Ошибка", "Не удалось запустить веб-сервер"); QMessageBox::warning(this, "Ошибка", "Не удалось запустить веб-сервер");
} }
} }
void FCreateNotify::setEditMode(bool isEditMode)
{
m_isEditMode = isEditMode;
if (isEditMode) {
ui->btnAdd->setText("Изменить");
setWindowTitle("TTW Bot app: Редактировать уведомления");
} else {
ui->btnAdd->setText("Создать");
setWindowTitle("TTW Bot app: Создать уведомления");
}
}
void FCreateNotify::loadExistingServer(HttpServer *server, const QString &name)
{
if (!server) return;
m_isEditMode = true;
m_server = server;
m_existingServerName = name;
setEditMode(true);
// Устанавливаем порт
FSettingsWS *settingsWS = ui->widget;
if (settingsWS) {
settingsWS->sbPort->setValue(server->port());
settingsWS->sbTime->setValue(server->getDuration());
}
// Устанавливаем название
ui->lineEdit->setText(name); // Предполагается, что есть lineEdit для имени
// Устанавливаем цвета
FColorSetting *colorSetting = ui->wBlock;
if (colorSetting) {
colorSetting->cbBlockColor->setCurrentText(server->getBlockColor());
colorSetting->cbBorderColor->setCurrentText(server->getBorderColor());
colorSetting->sbBorderSize->setValue(server->getBorderSize());
colorSetting->cbBackgroundColor->setCurrentText(server->getPageBackgroundColor());
colorSetting->hsBlockTransparant->setValue(server->getTransparency());
}
// Устанавливаем шрифты
FFontSetting *fontHeaderSetting = ui->wFont;
if (fontHeaderSetting) {
QString titleFamily;
int titleSize;
QString titleColor;
server->getTitleFont(titleFamily, titleSize, titleColor);
fontHeaderSetting->cbFontStyle->setCurrentText(titleFamily);
fontHeaderSetting->sbFontSize->setValue(titleSize);
fontHeaderSetting->cbFontColor->setCurrentText(titleColor);
}
FFontSetting *fontMessageSetting = ui->wFont_2;
if (fontMessageSetting) {
QString contentFamily;
int contentSize;
QString contentColor;
server->getContentFont(contentFamily, contentSize, contentColor);
fontMessageSetting->cbFontStyle->setCurrentText(contentFamily);
fontMessageSetting->sbFontSize->setValue(contentSize);
fontMessageSetting->cbFontColor->setCurrentText(contentColor);
}
}
// Обновите on_btnAdd_clicked для поддержки редактирования:
void FCreateNotify::on_btnAdd_clicked()
{
if (m_isEditMode && m_server) {
// Режим редактирования
FSettingsWS *settingsWS = ui->widget;
FColorSetting *colorSetting = ui->wBlock;
FFontSetting *fontHeaderSetting = ui->wFont;
FFontSetting *fontMessageSetting = ui->wFont_2;
if (!settingsWS || !colorSetting || !fontHeaderSetting || !fontMessageSetting) {
QMessageBox::warning(this, "Ошибка", "Не найдены настройки сервера");
return;
}
int newPort = settingsWS->sbPort->value();
bool portChanged = (newPort != m_server->port());
if (portChanged) {
m_server->stop();
delete m_server;
m_server = nullptr;
createServer();
}
if (!m_server) {
QMessageBox::warning(this, "Ошибка", "Не удалось обновить сервер");
return;
}
// Применяем настройки к серверу
// Цвета
m_server->setBlockColor(colorSetting->cbBlockColor->currentText());
m_server->setBorderColor(colorSetting->cbBorderColor->currentText());
m_server->setBorderSize(colorSetting->sbBorderSize->value());
m_server->setPageBackgroundColor(colorSetting->cbBackgroundColor->currentText());
m_server->setTransparency(colorSetting->hsBlockTransparant->value());
// Шрифты
m_server->setTitleFont(
fontHeaderSetting->cbFontStyle->currentText(),
fontHeaderSetting->sbFontSize->value(),
fontHeaderSetting->cbFontColor->currentText()
);
m_server->setContentFont(
fontMessageSetting->cbFontStyle->currentText(),
fontMessageSetting->sbFontSize->value(),
fontMessageSetting->cbFontColor->currentText()
);
// Длительность
m_server->setDuration(settingsWS->sbTime->value());
QString newName = ui->lineEdit->text();
if (newName.isEmpty()) {
newName = QString("Уведомления (порт %1)").arg(m_server->port());
}
emit serverUpdated(m_server, newName);
accept();
} else {
// Режим создания (существующий код)
createServer();
if (!m_server) {
QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер");
return;
}
createNotification(false);
QString name = ui->lineEdit->text();
if (name.isEmpty()) {
name = QString("Уведомления (порт %1)").arg(m_server->port());
}
emit serverCreated(m_server, name);
m_server = nullptr;
accept();
}
}
+9 -4
View File
@@ -3,6 +3,7 @@
#include "webservernotify.h" #include "webservernotify.h"
#include <QDialog> #include <QDialog>
#include "udatabase.h"
namespace Ui { namespace Ui {
class FCreateNotify; class FCreateNotify;
@@ -14,11 +15,12 @@ class FCreateNotify : public QDialog
signals: signals:
void serverCreated(HttpServer *server, const QString &name); void serverCreated(HttpServer *server, const QString &name);
void serverUpdated(HttpServer *server, const QString &name);
public: public:
explicit FCreateNotify(QWidget *parent = nullptr); explicit FCreateNotify(uDataBase *database = nullptr, QWidget *parent = nullptr);
~FCreateNotify(); ~FCreateNotify();
void loadExistingServer(HttpServer *server, const QString &name);
void setEditMode(bool isEditMode);
protected: protected:
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
@@ -34,11 +36,14 @@ private:
void createNotification(bool isTest = false); void createNotification(bool isTest = false);
QString getAbsolutePath(const QString &relativePath); QString getAbsolutePath(const QString &relativePath);
void copyFileToAppDir(const QString &sourcePath, const QString &destSubDir); void copyFileToAppDir(const QString &sourcePath, const QString &destSubDir);
bool m_isEditMode;
uDataBase *m_database;
QString m_existingServerName;
// Сервер // Сервер
HttpServer *m_server; HttpServer *m_server;
int m_notificationCounter; int m_notificationCounter;
void createServer(); void createServer();
void applyCurrentSettingsToServer();
}; };
#endif // FCREATENOTIFY_H #endif // FCREATENOTIFY_H
+23 -23
View File
@@ -137,7 +137,7 @@
<x>370</x> <x>370</x>
<y>150</y> <y>150</y>
<width>351</width> <width>351</width>
<height>181</height> <height>141</height>
</rect> </rect>
</property> </property>
<property name="title"> <property name="title">
@@ -149,7 +149,7 @@
<x>9</x> <x>9</x>
<y>19</y> <y>19</y>
<width>331</width> <width>331</width>
<height>152</height> <height>111</height>
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
@@ -163,8 +163,8 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="1" column="1">
<widget class="QPushButton" name="btnOpenSong"> <widget class="QPushButton" name="btnOpenImg">
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
</property> </property>
@@ -173,6 +173,13 @@
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLineEdit" name="edtFileImg"/> <widget class="QLineEdit" name="edtFileImg"/>
</item> </item>
<item row="3" column="1">
<widget class="QPushButton" name="btnOpenSong">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@@ -180,23 +187,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1">
<widget class="QPushButton" name="btnOpenImg">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Событие</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QComboBox" name="cbEvent"/>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
@@ -204,7 +194,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>370</x> <x>370</x>
<y>340</y> <y>300</y>
<width>80</width> <width>80</width>
<height>24</height> <height>24</height>
</rect> </rect>
@@ -217,7 +207,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>460</x> <x>460</x>
<y>340</y> <y>300</y>
<width>81</width> <width>81</width>
<height>24</height> <height>24</height>
</rect> </rect>
@@ -226,6 +216,16 @@
<string>Создать</string> <string>Создать</string>
</property> </property>
</widget> </widget>
<widget class="QLineEdit" name="lineEdit">
<property name="geometry">
<rect>
<x>370</x>
<y>400</y>
<width>231</width>
<height>22</height>
</rect>
</property>
</widget>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
+27 -39
View File
@@ -1,4 +1,5 @@
#include "ffontsetting.h" #include "ffontsetting.h"
#include "filemanager.h"
#include "qdebug.h" #include "qdebug.h"
#include "qdir.h" #include "qdir.h"
#include "ui_ffontsetting.h" #include "ui_ffontsetting.h"
@@ -48,58 +49,45 @@ void FFontSetting::setupColorComboBox(QComboBox* comboBox)
void FFontSetting::loadFonts() void FFontSetting::loadFonts()
{ {
QString fontsPath = "fonts"; // Загружаем шрифты из пользовательской папки
QDir fontsDir(fontsPath); QString userFontsPath = FileManager::instance().getPath(FileManager::Fonts);
QDir userFontsDir(userFontsPath);
if (fontsDir.exists()) {
QStringList fontFiles = fontsDir.entryList(QStringList() << "*.ttf" << "*.otf" << "*.ttc",
QDir::Files);
// Список всех загруженных шрифтов
QStringList allFontFamilies;
// Загружаем пользовательские шрифты
if (userFontsDir.exists()) {
QStringList fontFiles = userFontsDir.entryList(QStringList() << "*.ttf" << "*.otf" << "*.ttc", QDir::Files);
foreach (const QString &fontFile, fontFiles) { foreach (const QString &fontFile, fontFiles) {
QString fontPath = fontsDir.absoluteFilePath(fontFile); QString fontPath = userFontsDir.absoluteFilePath(fontFile);
int fontId = QFontDatabase::addApplicationFont(fontPath); int fontId = QFontDatabase::addApplicationFont(fontPath);
if (fontId != -1) { if (fontId != -1) {
QStringList fontFamilies = QFontDatabase::applicationFontFamilies(fontId); QStringList fontFamilies = QFontDatabase::applicationFontFamilies(fontId);
} else { allFontFamilies.append(fontFamilies);
} }
} }
}
// ОБНОВЛЯЕМ СПИСОК ШРИФТОВ В КОМБОБОКСЕ // Добавляем системные шрифты Windows
ui->cbFontStyle->clear(); // Очищаем старый список QFontDatabase fontDatabase;
QStringList systemFamilies = fontDatabase.families();
allFontFamilies.append(systemFamilies);
// Получаем все шрифты через объект QFontDatabase // Удаляем дубликаты
QFontDatabase fontDatabase; allFontFamilies.removeDuplicates();
QStringList fontFamilies = fontDatabase.families(); // Получаем все шрифты allFontFamilies.sort();
// ИЛИ используйте статический метод с параметром: // Заполняем ComboBox
// QStringList fontFamilies = QFontDatabase::families(QFontDatabase::Any); ui->cbFontStyle->clear();
ui->cbFontStyle->addItems(allFontFamilies);
ui->cbFontStyle->addItems(fontFamilies); // Добавляем все шрифты // Устанавливаем шрифт по умолчанию
int defaultIndex = ui->cbFontStyle->findText("Arial");
// Устанавливаем шрифт по умолчанию if (defaultIndex >= 0) {
int defaultIndex = ui->cbFontStyle->findText("Arial"); ui->cbFontStyle->setCurrentIndex(defaultIndex);
if (defaultIndex >= 0) {
ui->cbFontStyle->setCurrentIndex(defaultIndex);
}
// Показываем количество доступных шрифтов
} else {
// Загружаем системные шрифты, если папки нет
QFontDatabase fontDatabase;
QStringList fontFamilies = fontDatabase.families();
ui->cbFontStyle->clear();
ui->cbFontStyle->addItems(fontFamilies);
int defaultIndex = ui->cbFontStyle->findText("Arial");
if (defaultIndex >= 0) {
ui->cbFontStyle->setCurrentIndex(defaultIndex);
}
// Проверяем альтернативные пути
QString appDir = QApplication::applicationDirPath();
QString altPath = appDir + "/fonts";
} }
} }
+183
View File
@@ -0,0 +1,183 @@
#include "filemanager.h"
#include <QDebug>
FileManager::FileManager()
{
m_systemPath = QCoreApplication::applicationDirPath();
m_userDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
}
FileManager& FileManager::instance()
{
static FileManager instance;
return instance;
}
QString FileManager::systemPath() const
{
return m_systemPath;
}
QString FileManager::userDataPath() const
{
return m_userDataPath;
}
QString FileManager::getPath(FileType type, const QString& subPath) const
{
QString basePath;
// Определяем базовый путь (системный или пользовательский)
switch (type) {
case Icons:
case SystemStyles:
basePath = m_systemPath;
break;
default:
basePath = m_userDataPath;
break;
}
// Добавляем подпапку
QString folder;
switch (type) {
case Sounds: folder = "sounds"; break;
case Images: folder = "images"; break;
case Styles: folder = "styles"; break;
case Voices: folder = "voices"; break;
case Fonts: folder = "fonts"; break;
case Temp: folder = "temp"; break;
case Backups: folder = "backups"; break;
case Exports: folder = "exports"; break;
case Logs: folder = "logs"; break;
case Cache: folder = "cache"; break;
case WebServerImages: folder = "webserver/images"; break;
case WebServerSounds: folder = "webserver/sounds"; break;
case Icons: folder = "ico"; break;
case SystemStyles: folder = "styles"; break;
default: folder = "";
}
QString path = QString("%1/%2").arg(basePath).arg(folder);
if (!subPath.isEmpty()) {
path += "/" + subPath;
}
return path;
}
QString FileManager::getFullPath(FileType type, const QString& fileName) const
{
QString path = getPath(type);
return QString("%1/%2").arg(path).arg(fileName);
}
bool FileManager::copyToUserData(const QString& sourceFile, FileType type,
CopyMode mode, QString* newFileName)
{
QFileInfo sourceInfo(sourceFile);
if (!sourceInfo.exists()) {
qWarning() << "FileManager: Source file doesn't exist:" << sourceFile;
return false;
}
QString destFolder = getPath(type);
QDir dir(destFolder);
if (!dir.exists()) {
if (!dir.mkpath(".")) {
qWarning() << "FileManager: Cannot create folder:" << destFolder;
return false;
}
}
QString destFileName = sourceInfo.fileName();
QString destPath = getFullPath(type, destFileName);
// Обрабатываем конфликты имен
if (QFile::exists(destPath)) {
switch (mode) {
case CopyIfNotExists:
// Файл уже существует, не копируем
if (newFileName) *newFileName = destFileName;
return true;
case CopyAlways:
// Перезаписываем существующий файл
QFile::remove(destPath);
break;
case CopyWithNewName:
// Генерируем уникальное имя
int counter = 1;
QString baseName = sourceInfo.baseName();
QString suffix = sourceInfo.suffix();
while (QFile::exists(destPath)) {
destFileName = QString("%1_%2.%3").arg(baseName).arg(counter).arg(suffix);
destPath = getFullPath(type, destFileName);
counter++;
}
break;
}
}
// Копируем файл
if (QFile::copy(sourceFile, destPath)) {
if (newFileName) *newFileName = destFileName;
return true;
}
qWarning() << "FileManager: Failed to copy file from" << sourceFile << "to" << destPath;
return false;
}
bool FileManager::existsInUserData(FileType type, const QString& fileName) const
{
QString path = getFullPath(type, fileName);
return QFile::exists(path);
}
void FileManager::initializeFolderStructure()
{
// Создаем все пользовательские папки
for (int i = Sounds; i <= WebServerSounds; ++i) {
FileType type = static_cast<FileType>(i);
QString path = getPath(type);
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
}
}
QString FileManager::getWebPath(FileType type, const QString& fileName) const
{
QString folder;
switch (type) {
case WebServerImages: folder = "imgs"; break;
case WebServerSounds: folder = "sounds"; break;
default: return QString();
}
return QString("/%1/%2").arg(folder).arg(fileName);
}
void FileManager::copyDefaultFiles()
{
// Копируем системные стили в пользовательскую папку при первом запуске
QString systemStylesPath = getPath(SystemStyles);
QDir systemStylesDir(systemStylesPath);
if (systemStylesDir.exists()) {
QStringList qssFiles = systemStylesDir.entryList(QStringList() << "*.qss" << "*.QSS", QDir::Files);
for (const QString& file : qssFiles) {
QString sourcePath = systemStylesDir.absoluteFilePath(file);
QString destPath = getFullPath(Styles, file);
if (!QFile::exists(destPath)) {
QFile::copy(sourcePath, destPath);
}
}
}
}
+75
View File
@@ -0,0 +1,75 @@
#ifndef FILEMANAGER_H
#define FILEMANAGER_H
#include <QString>
#include <QDir>
#include <QFile>
#include <QStandardPaths>
#include <QCoreApplication>
class FileManager
{
public:
// Типы файлов/папок
enum FileType {
Sounds,
Images,
Styles,
Voices,
Fonts,
Temp,
Backups,
Exports,
Logs,
Cache,
WebServerImages,
WebServerSounds,
Icons,
SystemStyles
};
// Режим копирования
enum CopyMode {
CopyIfNotExists, // Копировать только если не существует
CopyAlways, // Всегда копировать (перезаписывать)
CopyWithNewName // Копировать с новым именем (если существует)
};
static FileManager& instance();
// Основные пути
QString systemPath() const;
QString userDataPath() const;
// Получение путей
QString getPath(FileType type, const QString& subPath = "") const;
QString getFullPath(FileType type, const QString& fileName) const;
// Копирование файлов
bool copyToUserData(const QString& sourceFile, FileType type,
CopyMode mode = CopyIfNotExists,
QString* newFileName = nullptr);
// Проверка существования
bool existsInUserData(FileType type, const QString& fileName) const;
// Инициализация структуры папок
void initializeFolderStructure();
// Получение относительного пути для веб-сервера
QString getWebPath(FileType type, const QString& fileName) const;
// Копирование файлов из системных в пользовательские (при первом запуске)
void copyDefaultFiles();
private:
FileManager();
~FileManager() = default;
FileManager(const FileManager&) = delete;
FileManager& operator=(const FileManager&) = delete;
QString m_systemPath;
QString m_userDataPath;
};
#endif // FILEMANAGER_H
+832
View File
@@ -0,0 +1,832 @@
#include "logmanager.h"
#include <QCoreApplication>
#include <QStandardPaths>
#include <QDir>
#include <QDateTime>
#include <QThread>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
// ============================================================================
// СТАТИЧЕСКИЕ ПЕРЕМЕННЫЕ
// ============================================================================
LogManager* LogManager::m_instance = nullptr;
// ============================================================================
// МЕТОДЫ LOGENTRY
// ============================================================================
QString LogEntry::toString() const
{
QStringList parts;
if (!timestamp.isNull()) {
parts.append(timestamp.toString("hh:mm:ss"));
}
parts.append(levelToString(level));
if (!module.isEmpty()) {
parts.append(module);
}
if (!method.isEmpty()) {
parts.append(method);
}
parts.append(message);
return parts.join(" | ");
}
QString LogEntry::toHtml() const
{
QString color = levelToColor(level).name();
QString html = QString(
"<div style=\"margin: 2px 0; padding: 2px; border-left: 3px solid %1;\">"
"<span style=\"color: gray; font-size: 0.8em;\">%2</span> "
"<span style=\"color: %1; font-weight: bold;\">[%3]</span> "
"<span style=\"color: darkblue;\">%4</span> "
"<span style=\"color: green;\">%5</span>: "
"<span>%6</span>"
"</div>")
.arg(color)
.arg(timestamp.toString("hh:mm:ss"))
.arg(levelToString(level))
.arg(module)
.arg(method)
.arg(message.toHtmlEscaped());
return html;
}
QString LogEntry::toCsv() const
{
return QString("\"%1\",\"%2\",\"%3\",\"%4\",\"%5\"")
.arg(timestamp.toString("yyyy-MM-dd hh:mm:ss"))
.arg(levelToString(level))
.arg(module)
.arg(method)
.arg(message);
}
QString LogEntry::toJson() const
{
QJsonObject obj;
obj["timestamp"] = timestamp.toString(Qt::ISODate);
obj["level"] = levelToString(level);
obj["module"] = module;
obj["method"] = method;
obj["message"] = message;
obj["threadId"] = static_cast<qint64>(threadId);
if (!sourceFile.isEmpty()) {
obj["sourceFile"] = sourceFile;
obj["sourceLine"] = sourceLine;
}
QJsonDocument doc(obj);
return doc.toJson(QJsonDocument::Compact);
}
QString LogEntry::levelToString(LogLevel level)
{
switch (level) {
case LogLevel::Info: return "INFO";
case LogLevel::Warning: return "WARNING";
case LogLevel::Error: return "ERROR";
case LogLevel::Debug: return "DEBUG";
case LogLevel::Critical: return "CRITICAL";
default: return "UNKNOWN";
}
}
QColor LogEntry::levelToColor(LogLevel level)
{
switch (level) {
case LogLevel::Info: return Qt::darkGreen;
case LogLevel::Warning: return Qt::darkYellow;
case LogLevel::Error: return Qt::red;
case LogLevel::Debug: return Qt::gray;
case LogLevel::Critical: return Qt::darkRed;
default: return Qt::black;
}
}
QString LogEntry::formatDateTime(const QDateTime& dt)
{
return dt.toString("dd.MM.yyyy hh:mm:ss.zzz");
}
// ============================================================================
// РЕАЛИЗАЦИЯ LOGMANAGER
// ============================================================================
LogManager::LogManager(QObject* parent)
: QObject(parent)
, m_maxEntries(10000)
{
// Настройки по умолчанию
m_settings.logToFile = true;
m_settings.logToConsole = true;
m_settings.logToDatabase = false;
// Стандартный путь к файлу логов
QString appDataPath = QStandardPaths::writableLocation(
QStandardPaths::AppDataLocation);
QDir dir(appDataPath);
if (!dir.exists()) {
dir.mkpath(".");
}
m_settings.logFilePath = dir.filePath("application.log");
// Цвета по умолчанию
m_settings.colors[LogLevel::Info] = Qt::darkGreen;
m_settings.colors[LogLevel::Warning] = Qt::darkYellow;
m_settings.colors[LogLevel::Error] = Qt::red;
m_settings.colors[LogLevel::Debug] = Qt::gray;
m_settings.colors[LogLevel::Critical] = Qt::darkRed;
m_settings.showTimestamp = true;
m_settings.showModule = true;
m_settings.showMethod = true;
}
LogManager::~LogManager()
{
if (m_logFile.isOpen()) {
m_logFile.close();
}
}
LogManager* LogManager::instance()
{
if (!m_instance) {
m_instance = new LogManager();
}
return m_instance;
}
void LogManager::initialize(const LogSettings& settings)
{
if (m_instance) {
m_instance->updateSettings(settings);
} else {
m_instance = new LogManager();
m_instance->updateSettings(settings);
}
}
void LogManager::cleanup()
{
if (m_instance) {
delete m_instance;
m_instance = nullptr;
}
}
void LogManager::log(LogLevel level, const QString& module,
const QString& method, const QString& message)
{
log(level, module, method, message, "", 0);
}
void LogManager::log(LogLevel level, const QString& module,
const QString& method, const QString& message,
const QString& sourceFile, int sourceLine)
{
QMutexLocker locker(&m_mutex);
// Создаем запись
LogEntry entry;
entry.timestamp = QDateTime::currentDateTime();
entry.level = level;
entry.module = module;
entry.method = method;
entry.message = message;
entry.threadId = reinterpret_cast<qint64>(QThread::currentThreadId());
if (!sourceFile.isEmpty()) {
entry.sourceFile = sourceFile;
entry.sourceLine = sourceLine;
}
// Добавляем в список
m_entries.append(entry);
// Ограничиваем количество записей
if (m_entries.size() > m_maxEntries) {
m_entries.removeFirst();
}
// Записываем в файл
if (m_settings.logToFile && !m_settings.logFilePath.isEmpty()) {
writeToFile(entry);
}
// Выводим в консоль
if (m_settings.logToConsole) {
QString formatted = formatForConsole(entry);
switch (level) {
case LogLevel::Error:
case LogLevel::Critical:
qCritical().noquote() << formatted;
break;
case LogLevel::Warning:
qWarning().noquote() << formatted;
break;
case LogLevel::Debug:
qDebug().noquote() << formatted;
break;
default:
qInfo().noquote() << formatted;
break;
}
}
locker.unlock();
// Отправляем сигналы
emit entryAdded(entry);
// Сигналы для конкретных уровней
switch (level) {
case LogLevel::Info:
emit infoAdded(entry);
break;
case LogLevel::Warning:
emit warningAdded(entry);
break;
case LogLevel::Error:
emit errorAdded(entry);
break;
case LogLevel::Debug:
emit debugAdded(entry);
break;
case LogLevel::Critical:
emit criticalAdded(entry);
break;
}
}
void LogManager::info(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Info, module, method, message);
}
void LogManager::warning(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Warning, module, method, message);
}
void LogManager::error(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Error, module, method, message);
}
void LogManager::debug(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Debug, module, method, message);
}
void LogManager::critical(const QString& module, const QString& method,
const QString& message)
{
log(LogLevel::Critical, module, method, message);
}
QList<LogEntry> LogManager::allEntries() const
{
QMutexLocker locker(&m_mutex);
return m_entries;
}
QList<LogEntry> LogManager::entriesByTime(const QDateTime& from,
const QDateTime& to) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
for (const auto& entry : m_entries) {
if (entry.timestamp >= from && entry.timestamp <= to) {
result.append(entry);
}
}
return result;
}
QList<LogEntry> LogManager::entriesByLevel(LogLevel level) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
for (const auto& entry : m_entries) {
if (entry.level == level) {
result.append(entry);
}
}
return result;
}
QList<LogEntry> LogManager::entriesByModule(const QString& module) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
for (const auto& entry : m_entries) {
if (entry.module == module) {
result.append(entry);
}
}
return result;
}
QList<LogEntry> LogManager::search(const QString& text, bool caseSensitive) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
Qt::CaseSensitivity sensitivity = caseSensitive ?
Qt::CaseSensitive : Qt::CaseInsensitive;
for (const auto& entry : m_entries) {
if (entry.message.contains(text, sensitivity) ||
entry.module.contains(text, sensitivity) ||
entry.method.contains(text, sensitivity)) {
result.append(entry);
}
}
return result;
}
QList<LogEntry> LogManager::filter(const QList<LogLevel>& levels,
const QString& moduleFilter,
const QString& methodFilter) const
{
QMutexLocker locker(&m_mutex);
QList<LogEntry> result;
for (const auto& entry : m_entries) {
// Проверка уровня
if (!levels.contains(entry.level)) {
continue;
}
// Проверка модуля
if (!moduleFilter.isEmpty() && entry.module != moduleFilter) {
continue;
}
// Проверка метода
if (!methodFilter.isEmpty() && entry.method != methodFilter) {
continue;
}
result.append(entry);
}
return result;
}
void LogManager::clear()
{
QMutexLocker locker(&m_mutex);
m_entries.clear();
locker.unlock();
emit logCleared();
}
bool LogManager::saveToFile(const QString& filePath, bool append)
{
QMutexLocker locker(&m_mutex);
QFile file(filePath);
QIODevice::OpenMode mode = QIODevice::WriteOnly | QIODevice::Text;
if (append) {
mode |= QIODevice::Append;
}
if (!file.open(mode)) {
emit fileError(QString("Не удалось открыть файл: %1").arg(file.errorString()));
return false;
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
// Заголовок
stream << "Дата,Уровень,Модуль,Метод,Сообщение\n";
for (const auto& entry : m_entries) {
stream << entry.toCsv() << "\n";
}
file.close();
return true;
}
bool LogManager::loadFromFile(const QString& filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
// Пропускаем заголовок
QString header = stream.readLine();
QList<LogEntry> loadedEntries;
while (!stream.atEnd()) {
QString line = stream.readLine();
QStringList parts = line.split('"');
if (parts.size() < 9) continue;
LogEntry entry;
entry.timestamp = QDateTime::fromString(parts[1], "yyyy-MM-dd hh:mm:ss");
QString levelStr = parts[3];
if (levelStr == "INFO") entry.level = LogLevel::Info;
else if (levelStr == "WARNING") entry.level = LogLevel::Warning;
else if (levelStr == "ERROR") entry.level = LogLevel::Error;
else if (levelStr == "DEBUG") entry.level = LogLevel::Debug;
else if (levelStr == "CRITICAL") entry.level = LogLevel::Critical;
entry.module = parts[5];
entry.method = parts[7];
entry.message = parts[9];
loadedEntries.append(entry);
}
QMutexLocker locker(&m_mutex);
m_entries = loadedEntries;
return true;
}
bool LogManager::exportToFormat(const QString& filePath, const QString& format)
{
QMutexLocker locker(&m_mutex);
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
if (format.toLower() == "html") {
stream << "<!DOCTYPE html>\n<html>\n<head>\n";
stream << "<meta charset=\"UTF-8\">\n";
stream << "<title>Логи приложения</title>\n";
stream << "<style>\n";
stream << "body { font-family: monospace; }\n";
stream << ".info { color: darkgreen; }\n";
stream << ".warning { color: darkorange; }\n";
stream << ".error { color: red; }\n";
stream << ".debug { color: gray; }\n";
stream << ".critical { color: darkred; }\n";
stream << "</style>\n";
stream << "</head>\n<body>\n";
for (const auto& entry : m_entries) {
stream << entry.toHtml() << "\n";
}
stream << "</body>\n</html>";
} else if (format.toLower() == "json") {
QJsonArray array;
for (const auto& entry : m_entries) {
QJsonObject obj;
obj["timestamp"] = entry.timestamp.toString(Qt::ISODate);
obj["level"] = LogEntry::levelToString(entry.level);
obj["module"] = entry.module;
obj["method"] = entry.method;
obj["message"] = entry.message;
array.append(obj);
}
QJsonDocument doc(array);
stream << doc.toJson();
} else { // TXT (по умолчанию)
for (const auto& entry : m_entries) {
stream << entry.toString() << "\n";
}
}
file.close();
return true;
}
QMap<LogLevel, int> LogManager::statistics() const
{
QMutexLocker locker(&m_mutex);
QMap<LogLevel, int> stats;
for (const auto& entry : m_entries) {
stats[entry.level]++;
}
return stats;
}
int LogManager::count() const
{
QMutexLocker locker(&m_mutex);
return m_entries.size();
}
int LogManager::maxEntries() const
{
QMutexLocker locker(&m_mutex);
return m_maxEntries;
}
void LogManager::setMaxEntries(int max)
{
QMutexLocker locker(&m_mutex);
m_maxEntries = max;
// Удаляем лишние записи
while (m_entries.size() > m_maxEntries) {
m_entries.removeFirst();
}
}
LogSettings LogManager::settings() const
{
QMutexLocker locker(&m_mutex);
return m_settings;
}
void LogManager::updateSettings(const LogSettings& newSettings)
{
QMutexLocker locker(&m_mutex);
// Закрываем старый файл, если изменился путь
if (m_settings.logFilePath != newSettings.logFilePath && m_logFile.isOpen()) {
m_logFile.close();
}
m_settings = newSettings;
// Инициализируем файл
if (m_settings.logToFile && !m_settings.logFilePath.isEmpty()) {
if (!m_logFile.isOpen()) {
m_logFile.setFileName(m_settings.logFilePath);
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString()));
} else {
m_logStream.setDevice(&m_logFile);
m_logStream.setCodec("UTF-8");
}
}
}
locker.unlock();
emit settingsChanged();
}
void LogManager::saveSettings(QSettings& settings)
{
QMutexLocker locker(&m_mutex);
settings.beginGroup("Logging");
settings.setValue("logToFile", m_settings.logToFile);
settings.setValue("logToConsole", m_settings.logToConsole);
settings.setValue("logFilePath", m_settings.logFilePath);
settings.setValue("maxFileSizeMB", m_settings.maxFileSizeMB);
settings.setValue("maxFileCount", m_settings.maxFileCount);
settings.setValue("showTimestamp", m_settings.showTimestamp);
settings.setValue("showModule", m_settings.showModule);
settings.setValue("showMethod", m_settings.showMethod);
settings.setValue("maxEntries", m_maxEntries);
settings.endGroup();
}
void LogManager::loadSettings(QSettings& settings)
{
QMutexLocker locker(&m_mutex);
settings.beginGroup("Logging");
m_settings.logToFile = settings.value("logToFile", true).toBool();
m_settings.logToConsole = settings.value("logToConsole", true).toBool();
m_settings.logFilePath = settings.value("logFilePath", m_settings.logFilePath).toString();
m_settings.maxFileSizeMB = settings.value("maxFileSizeMB", 10).toInt();
m_settings.maxFileCount = settings.value("maxFileCount", 5).toInt();
m_settings.showTimestamp = settings.value("showTimestamp", true).toBool();
m_settings.showModule = settings.value("showModule", true).toBool();
m_settings.showMethod = settings.value("showMethod", true).toBool();
m_maxEntries = settings.value("maxEntries", 10000).toInt();
settings.endGroup();
}
void LogManager::setLogToFileEnabled(bool enabled)
{
QMutexLocker locker(&m_mutex);
m_settings.logToFile = enabled;
if (enabled && !m_logFile.isOpen()) {
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString()));
}
} else if (!enabled && m_logFile.isOpen()) {
m_logFile.close();
}
}
void LogManager::setLogToConsoleEnabled(bool enabled)
{
QMutexLocker locker(&m_mutex);
m_settings.logToConsole = enabled;
}
void LogManager::setLogFilePath(const QString& path)
{
QMutexLocker locker(&m_mutex);
if (m_logFile.isOpen()) {
m_logFile.close();
}
m_settings.logFilePath = path;
if (m_settings.logToFile) {
m_logFile.setFileName(path);
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString()));
}
}
}
void LogManager::setLevelColors(const QMap<LogLevel, QColor>& colors)
{
QMutexLocker locker(&m_mutex);
m_settings.colors = colors;
}
void LogManager::setLevelColor(LogLevel level, const QColor& color)
{
QMutexLocker locker(&m_mutex);
m_settings.colors[level] = color;
}
void LogManager::setFormat(bool showTimestamp, bool showModule, bool showMethod)
{
QMutexLocker locker(&m_mutex);
m_settings.showTimestamp = showTimestamp;
m_settings.showModule = showModule;
m_settings.showMethod = showMethod;
}
void LogManager::writeToFile(const LogEntry& entry)
{
if (!m_logFile.isOpen()) {
return;
}
QString formatted = formatForFile(entry);
m_logStream << formatted << "\n";
m_logStream.flush();
// Проверяем размер файла
if (checkFileSize()) {
rotateLogFile();
}
}
void LogManager::rotateLogFile()
{
if (!m_logFile.isOpen()) {
return;
}
m_logFile.close();
QString basePath = m_settings.logFilePath;
QFileInfo fi(basePath);
QString baseName = fi.baseName();
QString suffix = fi.suffix();
QString dir = fi.path();
// Удаляем самый старый файл
QString oldestFile = QString("%1/%2.%3.%4")
.arg(dir)
.arg(baseName)
.arg(m_settings.maxFileCount)
.arg(suffix);
if (QFile::exists(oldestFile)) {
QFile::remove(oldestFile);
}
// Переименовываем остальные файлы
for (int i = m_settings.maxFileCount - 1; i >= 1; i--) {
QString oldFile = QString("%1/%2.%3.%4")
.arg(dir)
.arg(baseName)
.arg(i)
.arg(suffix);
QString newFile = QString("%1/%2.%3.%4")
.arg(dir)
.arg(baseName)
.arg(i + 1)
.arg(suffix);
if (QFile::exists(oldFile)) {
QFile::rename(oldFile, newFile);
}
}
// Переименовываем текущий файл
QString firstBackup = QString("%1/%2.%3.%4")
.arg(dir)
.arg(baseName)
.arg(1)
.arg(suffix);
QFile::rename(basePath, firstBackup);
// Открываем новый файл
if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
emit fileError(QString("Не удалось открыть файл логов после ротации: %1")
.arg(m_logFile.errorString()));
}
}
bool LogManager::checkFileSize()
{
if (!m_logFile.isOpen()) {
return false;
}
qint64 size = m_logFile.size();
qint64 maxSize = m_settings.maxFileSizeMB * 1024 * 1024;
return size > maxSize;
}
QString LogManager::formatEntry(const LogEntry& entry) const
{
QStringList parts;
if (m_settings.showTimestamp) {
parts.append(entry.timestamp.toString("hh:mm:ss"));
}
parts.append(LogEntry::levelToString(entry.level));
if (m_settings.showModule && !entry.module.isEmpty()) {
parts.append(entry.module);
}
if (m_settings.showMethod && !entry.method.isEmpty()) {
parts.append(entry.method);
}
parts.append(entry.message);
return parts.join(" | ");
}
QString LogManager::formatForConsole(const LogEntry& entry) const
{
QString formatted = formatEntry(entry);
// Добавляем цвет для консоли (если поддерживается)
if (m_settings.colors.contains(entry.level)) {
// Для консоли цвет добавляется через escape-последовательности
QColor color = m_settings.colors[entry.level];
// Это упрощенный вариант, можно расширить для реальной консоли
return formatted;
}
return formatted;
}
QString LogManager::formatForFile(const LogEntry& entry) const
{
// Для файла используем CSV формат
return entry.toCsv();
}
+394
View File
@@ -0,0 +1,394 @@
#ifndef LOGMANAGER_H
#define LOGMANAGER_H
#include <QObject>
#include <QDateTime>
#include <QColor>
#include <QList>
#include <QMutex>
#include <QFile>
#include <QTextStream>
#include <QSettings>
// ============================================================================
// ПЕРЕЧИСЛЕНИЯ И СТРУКТУРЫ
// ============================================================================
/**
* @brief Уровни логирования
*/
enum class LogLevel {
Info = 0, // Информационные сообщения
Warning = 1, // Предупреждения
Error = 2, // Ошибки
Debug = 3, // Отладочная информация
Critical = 4 // Критические ошибки
};
/**
* @brief Настройки логирования
*/
struct LogSettings {
bool logToFile = true; // Сохранять в файл
bool logToConsole = true; // Выводить в консоль
bool logToDatabase = false; // Сохранять в БД
QString logFilePath = ""; // Путь к файлу логов
int maxFileSizeMB = 10; // Максимальный размер файла (МБ)
int maxFileCount = 5; // Максимальное количество файлов
bool showTimestamp = true; // Показывать время
bool showModule = true; // Показывать модуль
bool showMethod = true; // Показывать метод
QMap<LogLevel, QColor> colors; // Цвета для уровней
};
/**
* @brief Запись лога
*/
struct LogEntry {
QDateTime timestamp; // Время записи
LogLevel level; // Уровень
QString module; // Модуль/компонент
QString method; // Метод/функция
QString message; // Сообщение
qint64 threadId = 0; // ID потока
QString sourceFile = ""; // Исходный файл
int sourceLine = 0; // Строка в файле
// Методы для форматирования
QString toString() const;
QString toHtml() const;
QString toCsv() const;
QString toJson() const;
// Статические методы
static QString levelToString(LogLevel level);
static QColor levelToColor(LogLevel level);
static QString formatDateTime(const QDateTime& dt);
};
// ============================================================================
// КЛАСС LOGMANAGER
// ============================================================================
/**
* @brief Менеджер логирования приложения
*/
class LogManager : public QObject
{
Q_OBJECT
public:
// ========================================================================
// СИНГЛТОН
// ========================================================================
/**
* @brief Получить экземпляр менеджера (синглтон)
*/
static LogManager* instance();
/**
* @brief Инициализировать менеджер
* @param settings Настройки логирования
*/
static void initialize(const LogSettings& settings = LogSettings());
/**
* @brief Очистить менеджер (освободить ресурсы)
*/
static void cleanup();
// ========================================================================
// ОСНОВНОЙ ИНТЕРФЕЙС
// ========================================================================
/**
* @brief Добавить запись в лог
* @param level Уровень логирования
* @param module Модуль/компонент
* @param method Метод/функция
* @param message Сообщение
*/
void log(LogLevel level, const QString& module,
const QString& method, const QString& message);
/**
* @brief Добавить запись в лог с информацией об исходном коде
*/
void log(LogLevel level, const QString& module,
const QString& method, const QString& message,
const QString& sourceFile, int sourceLine);
// Быстрые методы для разных уровней
void info(const QString& module, const QString& method,
const QString& message);
void warning(const QString& module, const QString& method,
const QString& message);
void error(const QString& module, const QString& method,
const QString& message);
void debug(const QString& module, const QString& method,
const QString& message);
void critical(const QString& module, const QString& method,
const QString& message);
// ========================================================================
// ФИЛЬТРАЦИЯ И ПОИСК
// ========================================================================
/**
* @brief Получить все записи
*/
QList<LogEntry> allEntries() const;
/**
* @brief Получить записи за указанный период
*/
QList<LogEntry> entriesByTime(const QDateTime& from,
const QDateTime& to) const;
/**
* @brief Получить записи по уровню
*/
QList<LogEntry> entriesByLevel(LogLevel level) const;
/**
* @brief Получить записи по модулю
*/
QList<LogEntry> entriesByModule(const QString& module) const;
/**
* @brief Поиск по тексту сообщения
*/
QList<LogEntry> search(const QString& text, bool caseSensitive = false) const;
/**
* @brief Применить фильтр
*/
QList<LogEntry> filter(const QList<LogLevel>& levels,
const QString& moduleFilter = "",
const QString& methodFilter = "") const;
// ========================================================================
// УПРАВЛЕНИЕ
// ========================================================================
/**
* @brief Очистить все записи
*/
void clear();
/**
* @brief Сохранить логи в файл
*/
bool saveToFile(const QString& filePath, bool append = false);
/**
* @brief Загрузить логи из файла
*/
bool loadFromFile(const QString& filePath);
/**
* @brief Экспортировать в формат
*/
bool exportToFormat(const QString& filePath, const QString& format = "txt");
/**
* @brief Получить статистику
*/
QMap<LogLevel, int> statistics() const;
/**
* @brief Получить количество записей
*/
int count() const;
/**
* @brief Получить максимальное количество записей в памяти
*/
int maxEntries() const;
/**
* @brief Установить максимальное количество записей в памяти
*/
void setMaxEntries(int max);
// ========================================================================
// НАСТРОЙКИ
// ========================================================================
/**
* @brief Получить текущие настройки
*/
LogSettings settings() const;
/**
* @brief Обновить настройки
*/
void updateSettings(const LogSettings& newSettings);
/**
* @brief Сохранить настройки в конфиг
*/
void saveSettings(QSettings& settings);
/**
* @brief Загрузить настройки из конфига
*/
void loadSettings(QSettings& settings);
// ========================================================================
// УТИЛИТЫ
// ========================================================================
/**
* @brief Включить/выключить логирование в файл
*/
void setLogToFileEnabled(bool enabled);
/**
* @brief Включить/выключить логирование в консоль
*/
void setLogToConsoleEnabled(bool enabled);
/**
* @brief Установить путь к файлу логов
*/
void setLogFilePath(const QString& path);
/**
* @brief Установить цвета для уровней
*/
void setLevelColors(const QMap<LogLevel, QColor>& colors);
/**
* @brief Установить цвет для уровня
*/
void setLevelColor(LogLevel level, const QColor& color);
/**
* @brief Установить формат вывода
*/
void setFormat(bool showTimestamp, bool showModule, bool showMethod);
signals:
// ========================================================================
// СИГНАЛЫ
// ========================================================================
/**
* @brief Добавлена новая запись в лог
*/
void entryAdded(const LogEntry& entry);
/**
* @brief Логи очищены
*/
void logCleared();
/**
* @brief Настройки изменены
*/
void settingsChanged();
/**
* @brief Ошибка при записи в файл
*/
void fileError(const QString& error);
// Сигналы для конкретных уровней (для удобства)
void infoAdded(const LogEntry& entry);
void warningAdded(const LogEntry& entry);
void errorAdded(const LogEntry& entry);
void debugAdded(const LogEntry& entry);
void criticalAdded(const LogEntry& entry);
private:
// ========================================================================
// ПРИВАТНЫЙ КОНСТРУКТОР (синглтон)
// ========================================================================
explicit LogManager(QObject* parent = nullptr);
~LogManager();
// ========================================================================
// ПРИВАТНЫЕ МЕТОДЫ
// ========================================================================
/**
* @brief Записать в файл
*/
void writeToFile(const LogEntry& entry);
/**
* @brief Ротация лог-файлов
*/
void rotateLogFile();
/**
* @brief Проверить размер файла
*/
bool checkFileSize();
/**
* @brief Форматирование записи для вывода
*/
QString formatEntry(const LogEntry& entry) const;
/**
* @brief Форматирование для консоли
*/
QString formatForConsole(const LogEntry& entry) const;
/**
* @brief Форматирование для файла
*/
QString formatForFile(const LogEntry& entry) const;
// ========================================================================
// ЧЛЕНЫ КЛАССА
// ========================================================================
static LogManager* m_instance; // Экземпляр синглтона
QList<LogEntry> m_entries; // Список записей
LogSettings m_settings; // Настройки
mutable QMutex m_mutex; // Мьютекс для потокобезопасности
QFile m_logFile; // Файл логов
QTextStream m_logStream; // Поток для записи в файл
int m_maxEntries = 10000; // Максимальное количество записей
// Предварительное объявление для Q_DISABLE_COPY
LogManager(const LogManager&) = delete;
LogManager& operator=(const LogManager&) = delete;
};
// ============================================================================
// МАКРОСЫ ДЛЯ УДОБСТВА
// ============================================================================
// Базовый макрос
#define LOG(level, module, method, message) \
LogManager::instance()->log(level, module, method, message, __FILE__, __LINE__)
// Макросы для уровней
#define LOG_INFO(module, method, message) \
LOG(LogLevel::Info, module, method, message)
#define LOG_WARNING(module, method, message) \
LOG(LogLevel::Warning, module, method, message)
#define LOG_ERROR(module, method, message) \
LOG(LogLevel::Error, module, method, message)
#define LOG_DEBUG(module, method, message) \
LOG(LogLevel::Debug, module, method, message)
#define LOG_CRITICAL(module, method, message) \
LOG(LogLevel::Critical, module, method, message)
// Макрос для проверки на отладку
#ifdef QT_DEBUG
#define LOG_DEBUG_ONLY(module, method, message) LOG_DEBUG(module, method, message)
#else
#define LOG_DEBUG_ONLY(module, method, message) Q_UNUSED(module); Q_UNUSED(method); Q_UNUSED(message)
#endif
#endif // LOGMANAGER_H
+101
View File
@@ -0,0 +1,101 @@
#include "MediaFileManager.h"
#include <QDebug>
MediaFileManager::MediaFileManager()
{
// Конструктор может быть использован для инициализации
}
bool MediaFileManager::addFile(const QString& name, const QString& filePath)
{
// Проверка на пустые значения
if (name.isEmpty() || filePath.isEmpty()) {
qWarning() << "Имя файла или путь не могут быть пустыми";
return false;
}
// Проверка на уникальность имени
if (contains(name)) {
qWarning() << "Файл с именем" << name << "уже существует";
return false;
}
// Добавление нового файла
mediaFiles.append(MediaFile(name, filePath));
qDebug() << "Файл" << name << "добавлен с путем:" << filePath;
return true;
}
bool MediaFileManager::removeFile(const QString& name)
{
int index = findFileIndex(name);
if (index == -1) {
qWarning() << "Файл с именем" << name << "не найден";
return false;
}
mediaFiles.remove(index);
qDebug() << "Файл" << name << "удален";
return true;
}
bool MediaFileManager::updateFile(const QString& oldName, const QString& newName, const QString& newFilePath)
{
int index = findFileIndex(oldName);
if (index == -1) {
qWarning() << "Файл с именем" << oldName << "не найден";
return false;
}
// Проверяем, не используется ли новое имя другим файлом
if (oldName != newName && contains(newName)) {
qWarning() << "Файл с именем" << newName << "уже существует";
return false;
}
// Обновляем информацию о файле
mediaFiles[index].name = newName;
mediaFiles[index].filePath = newFilePath;
qDebug() << "Файл обновлен:" << oldName << "->" << newName << "путь:" << newFilePath;
return true;
}
QString MediaFileManager::getFilePathByName(const QString& name) const
{
int index = findFileIndex(name);
if (index == -1) {
qWarning() << "Файл с именем" << name << "не найден";
return QString();
}
return mediaFiles[index].filePath;
}
int MediaFileManager::getFileCount() const
{
return mediaFiles.size();
}
QVector<MediaFile> MediaFileManager::getAllFiles() const
{
return mediaFiles;
}
bool MediaFileManager::contains(const QString& name) const
{
return findFileIndex(name) != -1;
}
int MediaFileManager::findFileIndex(const QString& name) const
{
for (int i = 0; i < mediaFiles.size(); ++i) {
if (mediaFiles[i].name == name) {
return i;
}
}
return -1;
}
+50
View File
@@ -0,0 +1,50 @@
#ifndef MEDIAFILEMANAGER_H
#define MEDIAFILEMANAGER_H
#include <QString>
#include <QVector>
// Структура для хранения информации о медиафайле
struct MediaFile {
QString name; // Имя файла
QString filePath; // Полный путь к файлу
MediaFile() = default;
MediaFile(const QString& name, const QString& filePath)
: name(name), filePath(filePath) {}
};
class MediaFileManager
{
public:
MediaFileManager();
// Добавление файла
bool addFile(const QString& name, const QString& filePath);
// Удаление файла по имени
bool removeFile(const QString& name);
// Изменение информации о файле
bool updateFile(const QString& oldName, const QString& newName, const QString& newFilePath);
// Получение пути к файлу по имени
QString getFilePathByName(const QString& name) const;
// Получение количества файлов
int getFileCount() const;
// Получение всех файлов (для отладки или отображения)
QVector<MediaFile> getAllFiles() const;
// Проверка существования файла по имени
bool contains(const QString& name) const;
private:
QVector<MediaFile> mediaFiles;
// Поиск индекса файла по имени
int findFileIndex(const QString& name) const;
};
#endif // MEDIAFILEMANAGER_H
+10
View File
@@ -1,12 +1,18 @@
debug/commandprocessor.o
debug/emoteprovider.o debug/emoteprovider.o
debug/fcolorsetting.o debug/fcolorsetting.o
debug/fcreatechat.o debug/fcreatechat.o
debug/fcreatenotify.o debug/fcreatenotify.o
debug/ffontsetting.o debug/ffontsetting.o
debug/filemanager.o
debug/fsettingsws.o debug/fsettingsws.o
debug/fsinglegrid.o debug/fsinglegrid.o
debug/logmanager.o
debug/main.o debug/main.o
debug/mediafilemanager.o
debug/neuralnetworkmanager.o debug/neuralnetworkmanager.o
debug/randommanager.o
debug/randomresponses.o
debug/soundmanager.o debug/soundmanager.o
debug/tauth.o debug/tauth.o
debug/ttw_api.o debug/ttw_api.o
@@ -20,6 +26,7 @@ debug/userwidget.o
debug/webserverchat.o debug/webserverchat.o
debug/webservernotify.o debug/webservernotify.o
debug/websocketclient.o debug/websocketclient.o
debug/moc_commandprocessor.o
debug/moc_emoteprovider.o debug/moc_emoteprovider.o
debug/moc_fcolorsetting.o debug/moc_fcolorsetting.o
debug/moc_fcreatechat.o debug/moc_fcreatechat.o
@@ -27,7 +34,10 @@ debug/moc_fcreatenotify.o
debug/moc_ffontsetting.o debug/moc_ffontsetting.o
debug/moc_fsettingsws.o debug/moc_fsettingsws.o
debug/moc_fsinglegrid.o debug/moc_fsinglegrid.o
debug/moc_logmanager.o
debug/moc_neuralnetworkmanager.o debug/moc_neuralnetworkmanager.o
debug/moc_randommanager.o
debug/moc_randomresponses.o
debug/moc_soundmanager.o debug/moc_soundmanager.o
debug/moc_tauth.o debug/moc_tauth.o
debug/moc_ttw_api.o debug/moc_ttw_api.o
+10
View File
@@ -1,12 +1,18 @@
release/commandprocessor.o
release/emoteprovider.o release/emoteprovider.o
release/fcolorsetting.o release/fcolorsetting.o
release/fcreatechat.o release/fcreatechat.o
release/fcreatenotify.o release/fcreatenotify.o
release/ffontsetting.o release/ffontsetting.o
release/filemanager.o
release/fsettingsws.o release/fsettingsws.o
release/fsinglegrid.o release/fsinglegrid.o
release/logmanager.o
release/main.o release/main.o
release/mediafilemanager.o
release/neuralnetworkmanager.o release/neuralnetworkmanager.o
release/randommanager.o
release/randomresponses.o
release/soundmanager.o release/soundmanager.o
release/tauth.o release/tauth.o
release/ttw_api.o release/ttw_api.o
@@ -20,6 +26,7 @@ release/userwidget.o
release/webserverchat.o release/webserverchat.o
release/webservernotify.o release/webservernotify.o
release/websocketclient.o release/websocketclient.o
release/moc_commandprocessor.o
release/moc_emoteprovider.o release/moc_emoteprovider.o
release/moc_fcolorsetting.o release/moc_fcolorsetting.o
release/moc_fcreatechat.o release/moc_fcreatechat.o
@@ -27,7 +34,10 @@ release/moc_fcreatenotify.o
release/moc_ffontsetting.o release/moc_ffontsetting.o
release/moc_fsettingsws.o release/moc_fsettingsws.o
release/moc_fsinglegrid.o release/moc_fsinglegrid.o
release/moc_logmanager.o
release/moc_neuralnetworkmanager.o release/moc_neuralnetworkmanager.o
release/moc_randommanager.o
release/moc_randomresponses.o
release/moc_soundmanager.o release/moc_soundmanager.o
release/moc_tauth.o release/moc_tauth.o
release/moc_ttw_api.o release/moc_ttw_api.o
+294
View File
@@ -0,0 +1,294 @@
#include "randommanager.h"
#include <QTableWidget>
#include <QDebug>
RandomManager::RandomManager(QObject *parent)
: QObject(parent)
, m_database(nullptr)
{
}
RandomManager::~RandomManager()
{
// Сохраняем данные при уничтожении
if (m_database) {
saveToDatabase();
}
}
bool RandomManager::initialize(uDataBase *database)
{
if (!database || !database->isConnected()) {
qWarning() << "RandomManager: База данных не подключена";
return false;
}
m_database = database;
return loadFromDatabase();
}
bool RandomManager::addRange(const QString &name, int startValue, int endValue)
{
// Проверка входных данных
if (name.isEmpty()) {
qWarning() << "RandomManager: Нельзя добавить диапазон с пустым именем";
return false;
}
if (!isValidRange(startValue, endValue)) {
qWarning() << "RandomManager: Некорректный диапазон:" << startValue << "-" << endValue;
return false;
}
// Проверка на дубликат
if (contains(name)) {
qWarning() << "RandomManager: Диапазон с именем" << name << "уже существует";
return false;
}
// Добавляем в список
RandomStruct newRange(name, startValue, endValue);
m_ranges.append(newRange);
// Сохраняем в БД
if (!saveToDatabase()) {
m_ranges.removeLast(); // Откатываем если не удалось сохранить
return false;
}
emit rangeAdded(name, startValue, endValue);
emit dataChanged();
qDebug() << "RandomManager: Добавлен диапазон" << name << startValue << "-" << endValue;
return true;
}
bool RandomManager::removeRange(const QString &name)
{
int index = findIndex(name);
if (index == -1) {
qWarning() << "RandomManager: Диапазон" << name << "не найден";
return false;
}
// Сохраняем данные для сигнала
RandomStruct removedRange = m_ranges.at(index);
// Удаляем из списка
m_ranges.removeAt(index);
// Сохраняем в БД
if (!saveToDatabase()) {
// Откатываем изменения
m_ranges.insert(index, removedRange);
return false;
}
emit rangeRemoved(name);
emit dataChanged();
qDebug() << "RandomManager: Удален диапазон" << name;
return true;
}
bool RandomManager::updateRange(const QString &oldName, const QString &newName,
int startValue, int endValue)
{
int index = findIndex(oldName);
if (index == -1) {
qWarning() << "RandomManager: Диапазон" << oldName << "не найден";
return false;
}
if (!isValidRange(startValue, endValue)) {
qWarning() << "RandomManager: Некорректный диапазон:" << startValue << "-" << endValue;
return false;
}
// Проверяем, не занято ли новое имя другим диапазоном (если имя изменилось)
if (oldName != newName && contains(newName)) {
qWarning() << "RandomManager: Имя" << newName << "уже занято";
return false;
}
// Сохраняем старые данные для отката
RandomStruct oldRange = m_ranges.at(index);
// Обновляем
m_ranges[index].name = newName;
m_ranges[index].startValue = startValue;
m_ranges[index].endValue = endValue;
// Сохраняем в БД
if (!saveToDatabase()) {
// Откатываем изменения
m_ranges[index] = oldRange;
return false;
}
emit rangeUpdated(oldName, newName);
emit dataChanged();
qDebug() << "RandomManager: Обновлен диапазон" << oldName << "->" << newName
<< startValue << "-" << endValue;
return true;
}
bool RandomManager::contains(const QString &name) const
{
return findIndex(name) != -1;
}
int RandomManager::getRandomValue(const QString &name) const
{
int index = findIndex(name);
if (index == -1) {
qWarning() << "RandomManager: Диапазон" << name << "не найден";
return 0;
}
const RandomStruct &range = m_ranges.at(index);
if (range.startValue == range.endValue) {
return range.startValue;
}
int min = qMin(range.startValue, range.endValue);
int max = qMax(range.startValue, range.endValue);
return QRandomGenerator::global()->bounded(min, max + 1);
}
QPair<int, int> RandomManager::getRange(const QString &name) const
{
int index = findIndex(name);
if (index == -1) {
return qMakePair(0, 0);
}
const RandomStruct &range = m_ranges.at(index);
return qMakePair(range.startValue, range.endValue);
}
bool RandomManager::saveToDatabase()
{
if (!m_database) {
qWarning() << "RandomManager: База данных не установлена";
return false;
}
// Создаем временную таблицу для сохранения
QTableWidget tempTable;
// Настраиваем таблицу
QStringList headers;
headers << "Имя" << "От" << "До";
tempTable.setColumnCount(headers.size());
tempTable.setHorizontalHeaderLabels(headers);
// Заполняем таблицу данными
tempTable.setRowCount(m_ranges.size());
for (int i = 0; i < m_ranges.size(); ++i) {
const RandomStruct &range = m_ranges.at(i);
// Имя
QTableWidgetItem *nameItem = new QTableWidgetItem(range.name);
tempTable.setItem(i, 0, nameItem);
// Начальное значение
QTableWidgetItem *startItem = new QTableWidgetItem(QString::number(range.startValue));
startItem->setTextAlignment(Qt::AlignCenter);
tempTable.setItem(i, 1, startItem);
// Конечное значение
QTableWidgetItem *endItem = new QTableWidgetItem(QString::number(range.endValue));
endItem->setTextAlignment(Qt::AlignCenter);
tempTable.setItem(i, 2, endItem);
}
// Сохраняем в БД
// Используем метод SaveTableWidget, который сохраняет данные в соответствующую таблицу
m_database->SaveTableWidget(&tempTable);
qDebug() << "RandomManager: Сохранено" << m_ranges.size() << "диапазонов в БД";
return true;
}
bool RandomManager::loadFromDatabase()
{
if (!m_database) {
qWarning() << "RandomManager: База данных не установлена";
return false;
}
// Создаем временную таблицу для загрузки
QTableWidget tempTable;
// Настраиваем таблицу
QStringList headers;
headers << "Имя" << "От" << "До";
tempTable.setColumnCount(headers.size());
tempTable.setHorizontalHeaderLabels(headers);
// Загружаем данные из БД
m_database->LoadTableWidget(&tempTable);
// Очищаем текущий список
m_ranges.clear();
// Заполняем список из таблицы
for (int row = 0; row < tempTable.rowCount(); ++row) {
QTableWidgetItem *nameItem = tempTable.item(row, 0);
QTableWidgetItem *startItem = tempTable.item(row, 1);
QTableWidgetItem *endItem = tempTable.item(row, 2);
if (!nameItem || !startItem || !endItem) {
qWarning() << "RandomManager: Пропущена строка" << row << "из-за отсутствия данных";
continue;
}
QString name = nameItem->text().trimmed();
if (name.isEmpty()) {
qWarning() << "RandomManager: Пропущена строка" << row << "с пустым именем";
continue;
}
bool okStart, okEnd;
int startValue = startItem->text().toInt(&okStart);
int endValue = endItem->text().toInt(&okEnd);
if (!okStart || !okEnd) {
qWarning() << "RandomManager: Некорректные числовые значения в строке" << row;
continue;
}
if (!isValidRange(startValue, endValue)) {
qWarning() << "RandomManager: Некорректный диапазон в строке" << row
<< startValue << "-" << endValue;
continue;
}
m_ranges.append(RandomStruct(name, startValue, endValue));
}
qDebug() << "RandomManager: Загружено" << m_ranges.size() << "диапазонов из БД";
emit dataChanged();
return true;
}
bool RandomManager::isValidRange(int start, int end) const
{
// Допускаются любые целые числа, даже если start > end
// Мы будем использовать qMin и qMax при генерации случайного числа
return true;
}
int RandomManager::findIndex(const QString &name) const
{
for (int i = 0; i < m_ranges.size(); ++i) {
if (m_ranges.at(i).name == name) {
return i;
}
}
return -1;
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef RANDOMMANAGER_H
#define RANDOMMANAGER_H
#include <QObject>
#include <QList>
#include <QString>
#include <QRandomGenerator>
#include "udatabase.h"
class RandomManager : public QObject
{
Q_OBJECT
public:
struct RandomStruct {
QString name;
int startValue;
int endValue;
RandomStruct() : startValue(0), endValue(0) {}
RandomStruct(const QString &name, int start, int end)
: name(name), startValue(start), endValue(end) {}
bool operator==(const QString &otherName) const { return name == otherName; }
};
explicit RandomManager(QObject *parent = nullptr);
~RandomManager();
// Основные методы
bool initialize(uDataBase *database);
// Работа с диапазонами
bool addRange(const QString &name, int startValue, int endValue);
bool removeRange(const QString &name);
bool updateRange(const QString &oldName, const QString &newName, int startValue, int endValue);
// Получение данных
QList<RandomStruct> getAllRanges() const { return m_ranges; }
bool contains(const QString &name) const;
int getRandomValue(const QString &name) const;
QPair<int, int> getRange(const QString &name) const;
// Сохранение/загрузка
bool saveToDatabase();
bool loadFromDatabase();
// Валидация
bool isValidRange(int start, int end) const;
signals:
void dataChanged();
void rangeAdded(const QString &name, int start, int end);
void rangeRemoved(const QString &name);
void rangeUpdated(const QString &oldName, const QString &newName);
private:
uDataBase *m_database;
QList<RandomStruct> m_ranges;
int findIndex(const QString &name) const;
};
#endif // RANDOMMANAGER_H
+103
View File
@@ -0,0 +1,103 @@
#include "RandomResponses.h"
#include <QDebug>
RandomResponses::RandomResponses(QObject *parent)
: QObject(parent)
, m_randomGenerator(QRandomGenerator::global()->generate())
{
}
QString RandomResponses::getResponse(const QString &groupName)
{
// Проверяем существование группы
if (!m_groups.contains(groupName)) {
qWarning() << "Группа" << groupName << "не найдена";
return QString();
}
const ResponseGroup &group = m_groups[groupName];
// Проверяем наличие ответов в группе
if (group.responses.isEmpty()) {
qWarning() << "Группа" << groupName << "не содержит ответов";
return QString();
}
// Генерируем случайный индекс
int randomIndex = m_randomGenerator.bounded(group.responses.size());
// Возвращаем случайный ответ
return group.responses.at(randomIndex);
}
bool RandomResponses::addResponse(const QString &groupName, const QString &response)
{
if (response.isEmpty()) {
qWarning() << "Пустой ответ не может быть добавлен";
return false;
}
// Если группа существует, добавляем ответ в существующую
if (m_groups.contains(groupName)) {
m_groups[groupName].responses.append(response);
}
// Иначе создаем новую группу
else {
ResponseGroup newGroup;
newGroup.name = groupName;
newGroup.responses.append(response);
m_groups.insert(groupName, newGroup);
}
return true;
}
bool RandomResponses::removeResponse(const QString &groupName, const QString &response)
{
// Проверяем существование группы
if (!m_groups.contains(groupName)) {
qWarning() << "Группа" << groupName << "не найдена";
return false;
}
ResponseGroup &group = m_groups[groupName];
// Удаляем все вхождения ответа
int removedCount = group.responses.removeAll(response);
// Если группа стала пустой, удаляем ее
if (group.responses.isEmpty()) {
m_groups.remove(groupName);
}
return removedCount > 0;
}
bool RandomResponses::removeGroup(const QString &groupName)
{
return m_groups.remove(groupName) > 0;
}
QStringList RandomResponses::getGroupNames() const
{
return m_groups.keys();
}
QStringList RandomResponses::getGroupResponses(const QString &groupName) const
{
if (m_groups.contains(groupName)) {
return m_groups[groupName].responses;
}
return QStringList();
}
bool RandomResponses::hasGroup(const QString &groupName) const
{
return m_groups.contains(groupName);
}
void RandomResponses::clear()
{
m_groups.clear();
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef RANDOMRESPONSES_H
#define RANDOMRESPONSES_H
#include <QObject>
#include <QString>
#include <QStringList>
#include <QMap>
#include <QList>
#include <QVector>
#include <QRandomGenerator>
// Структура для хранения группы ответов
struct ResponseGroup {
QString name; // Имя группы
QStringList responses; // Список возможных ответов
};
class RandomResponses : public QObject
{
Q_OBJECT
public:
explicit RandomResponses(QObject *parent = nullptr);
// Получить случайный ответ из группы
QString getResponse(const QString &groupName);
// Добавить группу или дополнить существующую
bool addResponse(const QString &groupName, const QString &response);
// Удалить конкретный ответ из группы
bool removeResponse(const QString &groupName, const QString &response);
// Удалить всю группу
bool removeGroup(const QString &groupName);
// Получить список всех групп
QStringList getGroupNames() const;
// Получить все ответы группы
QStringList getGroupResponses(const QString &groupName) const;
// Проверить существование группы
bool hasGroup(const QString &groupName) const;
// Очистить все данные
void clear();
private:
QMap<QString, ResponseGroup> m_groups; // Хранение групп по имени
QRandomGenerator m_randomGenerator; // Генератор случайных чисел
};
#endif // RANDOMRESPONSES_H
+2 -2
View File
@@ -323,7 +323,7 @@ void TTwAPI::banUserTime(const QString &id, int timeMinutes)
toLog(2, "TTwAPI::banUserTime", "Не удалось получить botId"); toLog(2, "TTwAPI::banUserTime", "Не удалось получить botId");
return; return;
} }
toLog(2, "TTwAPI::баним", id);
QJsonObject innerData; QJsonObject innerData;
innerData["user_id"] = id; innerData["user_id"] = id;
innerData["duration"] = timeMinutes * 60; innerData["duration"] = timeMinutes * 60;
@@ -503,7 +503,7 @@ void TTwAPI::toLog(int level, const QString &method, const QString &message)
Q_UNUSED(level); Q_UNUSED(level);
Q_UNUSED(method); Q_UNUSED(method);
Q_UNUSED(message); Q_UNUSED(message);
qDebug() << message;
// uGeneral.toLog("ttw_api", method, message, level); // uGeneral.toLog("ttw_api", method, message, level);
} }
+222
View File
@@ -1179,3 +1179,225 @@ bool uDataBase::updateChat(const QString &name, HttpServerChat *server,
return true; return true;
} }
bool uDataBase::createNotificationsTable()
{
QSqlQuery query(m_db);
QString sql =
"CREATE TABLE IF NOT EXISTS notifications ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" name TEXT NOT NULL,"
" type TEXT NOT NULL," // "notification"
" port INTEGER NOT NULL,"
" block_color TEXT,"
" border_color TEXT,"
" border_size INTEGER,"
" transparency INTEGER,"
" page_background_color TEXT,"
" title_family TEXT,"
" title_size INTEGER,"
" title_color TEXT,"
" content_family TEXT,"
" content_size INTEGER,"
" content_color TEXT,"
" duration INTEGER,"
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
")";
if (!query.exec(sql)) {
m_lastError = query.lastError().text();
qWarning() << "Failed to create notifications table:" << m_lastError;
return false;
}
// Создаем индекс для быстрого поиска по порту
sql = "CREATE INDEX IF NOT EXISTS idx_notifications_port ON notifications (port)";
query.exec(sql);
return true;
}
bool uDataBase::saveNotification(const QString &name, HttpServer *server)
{
if (!server) {
m_lastError = "Server is null";
return false;
}
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return false;
}
// Создаем таблицу если не существует
if (!tableExists("notifications")) {
if (!createNotificationsTable()) {
return false;
}
}
QSqlQuery query(m_db);
query.prepare(
"INSERT INTO notifications ("
" name, type, port, block_color, border_color, border_size,"
" transparency, page_background_color, title_family, title_size,"
" title_color, content_family, content_size, content_color, duration"
") VALUES ("
" :name, :type, :port, :block_color, :border_color, :border_size,"
" :transparency, :page_background_color, :title_family, :title_size,"
" :title_color, :content_family, :content_size, :content_color, :duration"
")"
);
// Используем реальные значения из сервера
query.bindValue(":name", name);
query.bindValue(":type", "notification");
query.bindValue(":port", server->port());
query.bindValue(":block_color", server->getBlockColor());
query.bindValue(":border_color", server->getBorderColor());
query.bindValue(":border_size", server->getBorderSize());
query.bindValue(":transparency", server->getTransparency());
query.bindValue(":page_background_color", server->getPageBackgroundColor());
query.bindValue(":title_family", server->getTitleFamily());
query.bindValue(":title_size", server->getTitleSize());
query.bindValue(":title_color", server->getTitleColor());
query.bindValue(":content_family", server->getContentFamily());
query.bindValue(":content_size", server->getContentSize());
query.bindValue(":content_color", server->getContentColor());
query.bindValue(":duration", server->getDuration());
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to save notification:" << m_lastError;
return false;
}
return true;
}
bool uDataBase::updateNotification(const QString &name, HttpServer *server, int oldPort)
{
if (!server) {
m_lastError = "Server is null";
return false;
}
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return false;
}
QSqlQuery query(m_db);
query.prepare(
"UPDATE notifications SET "
" name = :name, "
" port = :port, "
" block_color = :block_color, "
" border_color = :border_color, "
" border_size = :border_size, "
" transparency = :transparency, "
" page_background_color = :page_background_color, "
" title_family = :title_family, "
" title_size = :title_size, "
" title_color = :title_color, "
" content_family = :content_family, "
" content_size = :content_size, "
" content_color = :content_color, "
" duration = :duration "
"WHERE port = :old_port"
);
query.bindValue(":name", name);
query.bindValue(":port", server->port());
query.bindValue(":block_color", server->getBlockColor());
query.bindValue(":border_color", server->getBorderColor());
query.bindValue(":border_size", server->getBorderSize());
query.bindValue(":transparency", server->getTransparency());
query.bindValue(":page_background_color", server->getPageBackgroundColor());
query.bindValue(":title_family", server->getTitleFamily());
query.bindValue(":title_size", server->getTitleSize());
query.bindValue(":title_color", server->getTitleColor());
query.bindValue(":content_family", server->getContentFamily());
query.bindValue(":content_size", server->getContentSize());
query.bindValue(":content_color", server->getContentColor());
query.bindValue(":duration", server->getDuration());
query.bindValue(":old_port", oldPort);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to update notification:" << m_lastError;
return false;
}
return true;
}
QList<NotificationSettings> uDataBase::loadAllNotifications()
{
QList<NotificationSettings> notifications;
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return notifications;
}
if (!tableExists("notifications")) {
// Если таблицы нет, ничего не загружаем
return notifications;
}
QSqlQuery query(m_db);
QString sql = "SELECT * FROM notifications ORDER BY created_at";
if (!query.exec(sql)) {
m_lastError = query.lastError().text();
qWarning() << "Failed to load notifications:" << m_lastError;
return notifications;
}
while (query.next()) {
NotificationSettings settings;
settings.id = query.value("id").toInt();
settings.name = query.value("name").toString();
settings.type = query.value("type").toString();
settings.port = query.value("port").toInt();
settings.blockColor = query.value("block_color").toString();
settings.borderColor = query.value("border_color").toString();
settings.borderSize = query.value("border_size").toInt();
settings.transparency = query.value("transparency").toInt();
settings.pageBackgroundColor = query.value("page_background_color").toString();
settings.titleFamily = query.value("title_family").toString();
settings.titleSize = query.value("title_size").toInt();
settings.titleColor = query.value("title_color").toString();
settings.contentFamily = query.value("content_family").toString();
settings.contentSize = query.value("content_size").toInt();
settings.contentColor = query.value("content_color").toString();
settings.duration = query.value("duration").toInt();
settings.createdAt = query.value("created_at").toDateTime();
notifications.append(settings);
}
return notifications;
}
bool uDataBase::deleteNotification(int port)
{
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return false;
}
QSqlQuery query(m_db);
query.prepare("DELETE FROM notifications WHERE port = :port");
query.bindValue(":port", port);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to delete notification:" << m_lastError;
return false;
}
return true;
}
+29
View File
@@ -13,6 +13,7 @@
#include <QTableWidget> #include <QTableWidget>
#include "timerinfo.h" #include "timerinfo.h"
#include "webserverchat.h" #include "webserverchat.h"
#include "webservernotify.h"
struct ChatSettings { struct ChatSettings {
int id; int id;
@@ -35,6 +36,28 @@ struct ChatSettings {
bool deleteByTime; bool deleteByTime;
}; };
struct NotificationSettings {
int id;
QString name;
QString type;
int port;
QString blockColor;
QString borderColor;
int borderSize;
int transparency;
QString pageBackgroundColor;
QString titleFamily;
int titleSize;
QString titleColor;
QString contentFamily;
int contentSize;
QString contentColor;
int duration;
QDateTime createdAt;
};
class uDataBase : public QObject class uDataBase : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -84,6 +107,12 @@ public:
bool deleteChat(int port); bool deleteChat(int port);
QList<ChatSettings> loadAllChats(); QList<ChatSettings> loadAllChats();
// Методы для уведомлений
bool createNotificationsTable();
bool saveNotification(const QString &name, HttpServer *server);
bool updateNotification(const QString &name, HttpServer *server, int oldPort);
QList<NotificationSettings> loadAllNotifications();
bool deleteNotification(int port);
// Проверка подключения к БД // Проверка подключения к БД
bool isConnected() const; bool isConnected() const;
+611 -1052
View File
File diff suppressed because it is too large Load Diff
+30 -28
View File
@@ -9,8 +9,10 @@
#include <ulink.h> #include <ulink.h>
#include <udatabase.h> #include <udatabase.h>
#include <soundmanager.h> #include <soundmanager.h>
#include "commandprocessor.h"
#include "fcreatechat.h" #include "fcreatechat.h"
#include "fcreatenotify.h" #include "fcreatenotify.h"
#include "logmanager.h"
#include "neuralnetworkmanager.h" #include "neuralnetworkmanager.h"
#include "ttw_api.h" #include "ttw_api.h"
#include "user_manager.h" #include "user_manager.h"
@@ -71,17 +73,6 @@ public:
RListCommands(const QString &r1, const QString &r2) : R1(r1), R2(r2) {} RListCommands(const QString &r1, const QString &r2) : R1(r1), R2(r2) {}
}; };
// Запись лога
struct LogEntry {
QDateTime time;
QString module;
QString method;
QString message;
int code;
QString msgType;
QColor color;
};
// ======================================================================== // ========================================================================
// ПУБЛИЧНЫЕ ЧЛЕНЫ КЛАССА // ПУБЛИЧНЫЕ ЧЛЕНЫ КЛАССА
@@ -95,7 +86,7 @@ public:
TTwAPI *twitchAPI; // API для работы с Twitch TTwAPI *twitchAPI; // API для работы с Twitch
// Методы логирования и работы с командами // Методы логирования и работы с командами
void toLog(QString aModule, QString aMethod, QString aMessage, int aCode);
void toCommands(QString command, QString response); void toCommands(QString command, QString response);
private slots: private slots:
@@ -128,7 +119,6 @@ private slots:
void loadSettings(); // Загрузка настроек void loadSettings(); // Загрузка настроек
void on_btnImportSettings_clicked(); // Импорт настроек void on_btnImportSettings_clicked(); // Импорт настроек
void on_btnExportSettings_clicked(); // Экспорт настроек void on_btnExportSettings_clicked(); // Экспорт настроек
void on_btnSaveSettings_clicked(); // Сохранение настроек
// ======================================================================== // ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С УВЕДОМЛЕНИЯМИ // СЛОТЫ ДЛЯ РАБОТЫ С УВЕДОМЛЕНИЯМИ
@@ -159,12 +149,7 @@ private slots:
// СЛОТЫ ДЛЯ РАБОТЫ С КОМАНДАМИ И ОТВЕТАМИ // СЛОТЫ ДЛЯ РАБОТЫ С КОМАНДАМИ И ОТВЕТАМИ
// ======================================================================== // ========================================================================
void execCommand(const QString &sender, const QString &message); void execCommand(const QString &sender, const QString &message);
QString ResponsParserStatic(const QString &inMess, const QString &adName, const QString &aCommandText);
QString ResponsParserRandoms(const QString &inMess);
QString ResponsParserSounds(const QString &inMess);
QString ResponsParserText(const QString &inMess);
QString ResponsParserAI(const QString &response, const QString &pall);
QString responsParserAPI(const QString &inMess, const QString &adName);
// ======================================================================== // ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ИСКУССТВЕННЫМ ИНТЕЛЛЕКТОМ // СЛОТЫ ДЛЯ РАБОТЫ С ИСКУССТВЕННЫМ ИНТЕЛЛЕКТОМ
@@ -199,9 +184,13 @@ private slots:
// ======================================================================== // ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ЛОГАМИ // СЛОТЫ ДЛЯ РАБОТЫ С ЛОГАМИ
// ======================================================================== // ========================================================================
void applyLogFilter(); void onLogEntryAdded(const LogEntry& entry);
void onLogCleared();
// Добавляем методы для фильтрации
void addLogToTable(const LogEntry &entry, int row); void addLogToTable(const LogEntry &entry, int row);
bool shouldShowLogEntry(int code); void applyLogFilter();
bool shouldShowLogEntry(LogLevel level); // Изменяем тип параметра
// ======================================================================== // ========================================================================
// СЛОТЫ ДЛЯ РАБОТЫ С ТАБЛИЦАМИ И ГРИДАМИ // СЛОТЫ ДЛЯ РАБОТЫ С ТАБЛИЦАМИ И ГРИДАМИ
@@ -305,8 +294,6 @@ private slots:
void on_edtDACode_editingFinished(); void on_edtDACode_editingFinished();
void on_cbDAAutoLogin_checkStateChanged(const Qt::CheckState &arg1);
void on_cbDAAutoLogin_stateChanged(int arg1); void on_cbDAAutoLogin_stateChanged(int arg1);
void on_edtGPTPrefix_editingFinished(); void on_edtGPTPrefix_editingFinished();
@@ -359,6 +346,7 @@ private slots:
void on_btnThemesFolder_clicked(); void on_btnThemesFolder_clicked();
void onNotifyServerUpdated(HttpServer *server, const QString &name);
public slots: public slots:
// Установка статуса подключения к Twitch // Установка статуса подключения к Twitch
void setTwitchConnected(bool connected); void setTwitchConnected(bool connected);
@@ -372,15 +360,17 @@ private:
TAuth *m_authBot; // Авторизация бота TAuth *m_authBot; // Авторизация бота
TAuth *m_authStreamer; // Авторизация стримера TAuth *m_authStreamer; // Авторизация стримера
TAuth *m_authDA; // Авторизация DA TAuth *m_authDA; // Авторизация DA
RandomResponses *m_randomResponses;
uLink *fLinkForm; // Форма ссылок uLink *fLinkForm; // Форма ссылок
TTTVAuth *TTVAuth; // Данные авторизации Twitch TTTVAuth *TTVAuth; // Данные авторизации Twitch
UserManager *m_userManager; // Менеджер пользователей UserManager *m_userManager; // Менеджер пользователей
QList<LogEntry> allLogs; // Все записи логов CommandProcessor* m_commandProcessor; // Процессор команд
WebSocketClient *m_twitchClient; // WebSocket клиент для Twitch WebSocketClient *m_twitchClient; // WebSocket клиент для Twitch
UserWidget* m_userWidget; // Виджет пользователя UserWidget* m_userWidget; // Виджет пользователя
NeuralNetworkManager *nnManager; // Менеджер нейронных сетей NeuralNetworkManager *nnManager; // Менеджер нейронных сетей
RandomManager *m_randomManager;
MediaFileManager *m_SoundFiles;
MediaFileManager *m_TextFiles;
QList<TimerInfo> m_timers; // Список таймеров QList<TimerInfo> m_timers; // Список таймеров
int m_nextTimerId = 1; // Следующий ID таймера int m_nextTimerId = 1; // Следующий ID таймера
@@ -430,7 +420,7 @@ private:
// Метод для применения QSS темы // Метод для применения QSS темы
void applyStyleSheet(const QString &filename); void applyStyleSheet(const QString &filename);
QString getUserFilePath(const QString &type, const QString &fileName) const;
QString getUserDataPath() const; QString getUserDataPath() const;
QString getSystemPath() const; QString getSystemPath() const;
void setupButtonIcons(); void setupButtonIcons();
@@ -455,7 +445,10 @@ private:
// Воспроизведение уведомлений // Воспроизведение уведомлений
void playNotify(const bool &isMod, const bool &isVip, const bool &isSub); void playNotify(const bool &isMod, const bool &isVip, const bool &isSub);
void loadCommandsFromTableWidget();
void loadRandomRangesFromTableWidget();
void loadRandomResponseGroupsFromDatabase();
void loadSoundsFromDatabase();
// Инициализация базы данных // Инициализация базы данных
void initializeDatabase(); void initializeDatabase();
// Настройка пользовательского интерфейса // Настройка пользовательского интерфейса
@@ -476,10 +469,19 @@ private:
void loadEmoties(); void loadEmoties();
void loadChatBadges(); void loadChatBadges();
/**
* @brief Инициализирует звуковые уведомления
*/
void initializeNotificationSounds();
// Обработка эмодзи // Обработка эмодзи
void processEmotes(QString &message); void processEmotes(QString &message);
void loadSavedChats(); void loadSavedChats();
void loadSavedNotifications();
void processUserCommand(const QString &username, const QString &commandText);
void sendChatResponse(const QString &response);
}; };
#endif // UGENERAL_H #endif // UGENERAL_H
+21 -4
View File
@@ -6,6 +6,7 @@
#include <QJsonObject> #include <QJsonObject>
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QRandomGenerator>
UserManager::UserManager(QObject *parent) UserManager::UserManager(QObject *parent)
: QObject(parent) : QObject(parent)
@@ -295,16 +296,32 @@ QVector<User*> UserManager::findUsersByLogin(const QString &login)
return result; return result;
} }
User* UserManager::getUserByIndex(int index) User* UserManager::getUserByIndex(const QString& userID)
{ {
if (index >= 0 && index < m_users.size()) { auto it = m_users.find(userID);
auto it = m_users.begin(); if (it != m_users.end()) {
std::advance(it, index);
return &it.value(); return &it.value();
} }
return nullptr; return nullptr;
} }
const User* UserManager::getRandomUser() const {
if (m_users.isEmpty()) {
return nullptr;
}
QList<QString> keys = m_users.keys();
int randomIndex = QRandomGenerator::global()->bounded(keys.size());
// Возвращаем указатель на существующий объект в контейнере
const auto it = m_users.constFind(keys[randomIndex]);
if (it != m_users.constEnd()) {
return &(it.value()); // Указатель на существующий объект
}
return nullptr;
}
const QMap<QString, User>& UserManager::getAllUsers() const const QMap<QString, User>& UserManager::getAllUsers() const
{ {
return m_users; return m_users;
+2 -2
View File
@@ -38,7 +38,7 @@ public:
QVector<User*> getVIPs() const; QVector<User*> getVIPs() const;
QVector<User*> getSubscribers() const; QVector<User*> getSubscribers() const;
QVector<User*> getActiveUsers(int minutes = 10) const; QVector<User*> getActiveUsers(int minutes = 10) const;
const User* getRandomUser() const;
// Статистика // Статистика
int getUserCount() const; int getUserCount() const;
int getMessageCount() const; int getMessageCount() const;
@@ -48,7 +48,7 @@ public:
bool loadFromFile(const QString &filename); bool loadFromFile(const QString &filename);
// Геттеры // Геттеры
User* getUserByIndex(int index); User* getUserByIndex(const QString &userID);
const QMap<QString, User>& getAllUsers() const; const QMap<QString, User>& getAllUsers() const;
signals: signals:
+66 -6
View File
@@ -10,6 +10,17 @@ HttpServer::HttpServer(QObject *parent)
: QObject(parent) : QObject(parent)
, m_server(nullptr) , m_server(nullptr)
, m_pageBackgroundColor("transparent") , m_pageBackgroundColor("transparent")
, m_blockColor("#FFFFFF")
, m_borderColor("#000000")
, m_borderSize(2)
, m_transparency(0)
, m_titleFamily("Arial")
, m_titleSize(20)
, m_titleColor("#FFFFFF")
, m_contentFamily("Arial")
, m_contentSize(16)
, m_contentColor("#F5F5F5")
, m_duration(10)
{ {
} }
@@ -78,8 +89,6 @@ void HttpServer::addNotification(const Notification &notification)
m_notifications.append(notif); m_notifications.append(notif);
// Обновляем всех подключенных клиентов // Обновляем всех подключенных клиентов
for (auto client : m_clients) { for (auto client : m_clients) {
if (client->state() == QAbstractSocket::ConnectedState) { if (client->state() == QAbstractSocket::ConnectedState) {
@@ -236,7 +245,7 @@ QString HttpServer::generateHTML()
"<meta charset='UTF-8'>\n" "<meta charset='UTF-8'>\n"
"<title>Web Notifications</title>\n" "<title>Web Notifications</title>\n"
"<style>\n" "<style>\n"
"body { background: %1; margin: 0; padding: 0; }\n" // Используем цвет фона страницы "body { background: %1; margin: 0; padding: 0; }\n" // Используем цвет фона страницы
"#messages { position: fixed; top: 20px; right: 20px; width: 400px; }\n" "#messages { position: fixed; top: 20px; right: 20px; width: 400px; }\n"
".notification { \n" ".notification { \n"
" margin: 10px 0; \n" " margin: 10px 0; \n"
@@ -257,9 +266,9 @@ QString HttpServer::generateHTML()
" from { opacity: 0; transform: translateY(-20px); }\n" " from { opacity: 0; transform: translateY(-20px); }\n"
" to { opacity: 1; transform: translateY(0); }\n" " to { opacity: 1; transform: translateY(0); }\n"
"}\n" "}\n"
"</style>\n" "</style>\n"
"</head>\n" "</head>\n"
"<body>\n" "<body>\n"
"<div id='messages'></div>\n" "<div id='messages'></div>\n"
"<script>\n" "<script>\n"
"let notifications = new Map(); // Храним активные уведомления по timestamp\n" "let notifications = new Map(); // Храним активные уведомления по timestamp\n"
@@ -505,3 +514,54 @@ void HttpServer::discardClient()
socket->deleteLater(); socket->deleteLater();
} }
} }
void HttpServer::setBlockColor(const QString &color) { m_blockColor = color; }
QString HttpServer::getBlockColor() const { return m_blockColor; }
void HttpServer::setBorderColor(const QString &color) { m_borderColor = color; }
QString HttpServer::getBorderColor() const { return m_borderColor; }
void HttpServer::setBorderSize(int size) { m_borderSize = size; }
int HttpServer::getBorderSize() const { return m_borderSize; }
void HttpServer::setTransparency(int transparency) { m_transparency = transparency; }
int HttpServer::getTransparency() const { return m_transparency; }
void HttpServer::setPageBackgroundColor(const QString &color) { m_pageBackgroundColor = color; }
QString HttpServer::getPageBackgroundColor() const { return m_pageBackgroundColor; }
QString HttpServer::getTitleFamily() const { return m_titleFamily; }
int HttpServer::getTitleSize() const { return m_titleSize; }
QString HttpServer::getTitleColor() const { return m_titleColor; }
QString HttpServer::getContentFamily() const { return m_contentFamily; }
int HttpServer::getContentSize() const { return m_contentSize; }
QString HttpServer::getContentColor() const { return m_contentColor; }
void HttpServer::setTitleFont(const QString &family, int size, const QString &color) {
m_titleFamily = family;
m_titleSize = size;
m_titleColor = color;
}
void HttpServer::getTitleFont(QString &family, int &size, QString &color) const {
family = m_titleFamily;
size = m_titleSize;
color = m_titleColor;
}
void HttpServer::setContentFont(const QString &family, int size, const QString &color) {
m_contentFamily = family;
m_contentSize = size;
m_contentColor = color;
}
void HttpServer::getContentFont(QString &family, int &size, QString &color) const {
family = m_contentFamily;
size = m_contentSize;
color = m_contentColor;
}
void HttpServer::setDuration(int duration) { m_duration = duration; }
int HttpServer::getDuration() const { return m_duration; }
+40
View File
@@ -57,6 +57,33 @@ public:
void addNotification(const Notification &notification); void addNotification(const Notification &notification);
void clearNotifications(); void clearNotifications();
// Методы для доступа к настройкам (нужно реализовать хранение)
void setBlockColor(const QString &color);
QString getBlockColor() const;
void setBorderColor(const QString &color);
QString getBorderColor() const;
void setBorderSize(int size);
int getBorderSize() const;
void setTransparency(int transparency);
int getTransparency() const;
void setPageBackgroundColor(const QString &color);
QString getPageBackgroundColor() const;
void setTitleFont(const QString &family, int size, const QString &color);
void getTitleFont(QString &family, int &size, QString &color) const;
void setContentFont(const QString &family, int size, const QString &color);
void getContentFont(QString &family, int &size, QString &color) const;
void setDuration(int duration);
int getDuration() const;
QString getTitleFamily() const;
int getTitleSize() const;
QString getTitleColor() const;
QString getContentFamily() const;
int getContentSize() const;
QString getContentColor() const;
signals: signals:
void serverStarted(bool success); void serverStarted(bool success);
@@ -71,6 +98,19 @@ private:
QList<Notification> m_notifications; QList<Notification> m_notifications;
QString m_pageBackgroundColor; // Цвет фона страницы QString m_pageBackgroundColor; // Цвет фона страницы
QString m_blockColor;
QString m_borderColor;
int m_borderSize;
int m_transparency;
QString m_titleFamily;
int m_titleSize;
QString m_titleColor;
QString m_contentFamily;
int m_contentSize;
QString m_contentColor;
int m_duration;
void processRequest(QTcpSocket *socket, const QString &request); void processRequest(QTcpSocket *socket, const QString &request);
void sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode = 200); void sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode = 200);
void sendHtmlPage(QTcpSocket *socket); void sendHtmlPage(QTcpSocket *socket);