создал менеджер донатов

- добавление
- удаление
- сохранение
This commit is contained in:
2026-02-22 09:18:00 +03:00
parent 05662be287
commit eb494ae8fa
10 changed files with 499 additions and 11 deletions
+2
View File
@@ -24,6 +24,7 @@ SOURCES += \
actionmanager.cpp \
commandprocessor.cpp \
countermanager.cpp \
donationmanager.cpp \
emoteprovider.cpp \
fcolorsetting.cpp \
fcreatechat.cpp \
@@ -59,6 +60,7 @@ HEADERS += \
badge.h \
commandprocessor.h \
countermanager.h \
donationmanager.h \
emoteprovider.h \
fcolorsetting.h \
fcreatechat.h \
+175
View File
@@ -0,0 +1,175 @@
#include "donationmanager.h"
#include "udatabase.h"
#include <QRegularExpression>
#include <QDebug>
DonationManager::DonationManager(uDataBase *db, QObject *parent)
: QObject(parent)
, m_db(db)
{
}
bool DonationManager::loadFromDatabase()
{
if (!m_db) return false;
// Предполагаем, что в uDataBase есть метод loadAllDonationTriggers()
m_triggers = m_db->loadAllDonationTriggers();
return true;
}
bool DonationManager::addTrigger(const QString &name, const QString &rule)
{
if (name.isEmpty() || rule.isEmpty()) return false;
DonationTrigger trig;
trig.name = name;
trig.rule = rule;
if (!parseRule(rule, trig)) {
qWarning() << "Invalid rule:" << rule;
return false;
}
// Сохраняем в БД (метод saveDonationTrigger должен вернуть id)
int newId = m_db->saveDonationTrigger(trig);
if (newId < 0) return false;
trig.id = newId;
m_triggers.append(trig);
emit dataChanged();
return true;
}
bool DonationManager::deleteTrigger(int id)
{
if (!m_db->deleteDonationTrigger(id)) return false;
for (int i = 0; i < m_triggers.size(); ++i) {
if (m_triggers[i].id == id) {
m_triggers.removeAt(i);
break;
}
}
emit dataChanged();
return true;
}
QList<DonationTrigger> DonationManager::getAllTriggers() const
{
return m_triggers;
}
QString DonationManager::matchDonation(double amount) const
{
const DonationTrigger *best = nullptr;
int bestPriority = 999; // чем меньше, тем выше приоритет
for (const DonationTrigger &t : m_triggers) {
bool ok = false;
switch (t.priority) {
case 1: // exact
if (qFuzzyCompare(amount, t.minValue))
ok = true;
break;
case 2: // range
if (amount >= t.minValue && amount <= t.maxValue)
ok = true;
break;
case 3: // greater / greater-equal
if (t.isGreaterEqual) {
if (amount >= t.minValue) ok = true;
} else {
if (amount > t.minValue) ok = true;
}
break;
default:
continue;
}
if (ok) {
// Сравниваем приоритет
if (t.priority < bestPriority) {
best = &t;
bestPriority = t.priority;
}
// Если приоритет одинаковый, применяем дополнительные правила:
else if (t.priority == bestPriority) {
if (bestPriority == 2) {
// Для диапазонов выбираем более узкий (меньше разница max-min)
double bestRange = best->maxValue - best->minValue;
double thisRange = t.maxValue - t.minValue;
if (thisRange < bestRange) {
best = &t;
}
}
else if (bestPriority == 3) {
// Для больше/больше-равно выбираем наибольшее пороговое значение (minValue)
if (t.minValue > best->minValue) {
best = &t;
}
}
// Для exact приоритет 1 – только одно значение, совпадение уже есть.
}
}
}
return best ? best->name : QString();
}
bool DonationManager::parseRule(const QString &rule, DonationTrigger &t) const
{
QString r = rule.trimmed();
if (r.isEmpty()) return false;
// Точное равенство: =123
if (r.startsWith('=')) {
QString numStr = r.mid(1);
bool ok;
double val = numStr.toDouble(&ok);
if (!ok) return false;
t.priority = 1;
t.minValue = val;
t.maxValue = val;
return true;
}
// Диапазон: 100-200
if (r.contains('-')) {
QStringList parts = r.split('-', Qt::SkipEmptyParts);
if (parts.size() != 2) return false;
bool ok1, ok2;
double a = parts[0].toDouble(&ok1);
double b = parts[1].toDouble(&ok2);
if (!ok1 || !ok2 || a > b) return false;
t.priority = 2;
t.minValue = a;
t.maxValue = b;
return true;
}
// Больше/больше-равно
if (r.startsWith(">=")) {
QString numStr = r.mid(2);
bool ok;
double val = numStr.toDouble(&ok);
if (!ok) return false;
t.priority = 3;
t.minValue = val;
t.maxValue = 0;
t.isGreaterEqual = true;
return true;
}
if (r.startsWith('>')) {
QString numStr = r.mid(1);
bool ok;
double val = numStr.toDouble(&ok);
if (!ok) return false;
t.priority = 3;
t.minValue = val;
t.maxValue = 0;
t.isGreaterEqual = false;
return true;
}
return false;
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef DONATIONMANAGER_H
#define DONATIONMANAGER_H
#include <QObject>
#include <QList>
#include <QString>
class uDataBase;
struct DonationTrigger
{
int id = -1;
QString name; // Название триггера (например "Малый донат")
QString rule; // Строка правила: "=100", ">1000", "100-200" и т.д.
int priority = 0; // Вычисляется при парсинге (1 – exact, 2 range, 3 greater/ge)
double minValue = 0; // Для численных сравнений
double maxValue = 0; // Для диапазона (если range)
bool isGreaterEqual = false; // true для ">="
};
class DonationManager : public QObject
{
Q_OBJECT
public:
explicit DonationManager(uDataBase *db, QObject *parent = nullptr);
// Загрузить все триггеры из БД
bool loadFromDatabase();
// Сохранить новый триггер в БД и добавить в список
bool addTrigger(const QString &name, const QString &rule);
// Удалить триггер по ID (и из БД, и из списка)
bool deleteTrigger(int id);
// Получить все триггеры (для отображения)
QList<DonationTrigger> getAllTriggers() const;
// Поиск подходящего триггера по сумме доната
// Возвращает имя триггера или пустую строку
QString matchDonation(double amount) const;
signals:
void dataChanged(); // для обновления таблицы
private:
// Парсинг строки правила, заполняет поля min, max, priority, isGreaterEqual
bool parseRule(const QString &rule, DonationTrigger &trigger) const;
uDataBase *m_db;
QList<DonationTrigger> m_triggers;
};
#endif // DONATIONMANAGER_H
+2
View File
@@ -1,6 +1,7 @@
debug/actionmanager.o
debug/commandprocessor.o
debug/countermanager.o
debug/donationmanager.o
debug/emoteprovider.o
debug/fcolorsetting.o
debug/fcreatechat.o
@@ -32,6 +33,7 @@ debug/websocketclient.o
debug/moc_actionmanager.o
debug/moc_commandprocessor.o
debug/moc_countermanager.o
debug/moc_donationmanager.o
debug/moc_emoteprovider.o
debug/moc_fcolorsetting.o
debug/moc_fcreatechat.o
+2
View File
@@ -1,6 +1,7 @@
release/actionmanager.o
release/commandprocessor.o
release/countermanager.o
release/donationmanager.o
release/emoteprovider.o
release/fcolorsetting.o
release/fcreatechat.o
@@ -32,6 +33,7 @@ release/websocketclient.o
release/moc_actionmanager.o
release/moc_commandprocessor.o
release/moc_countermanager.o
release/moc_donationmanager.o
release/moc_emoteprovider.o
release/moc_fcolorsetting.o
release/moc_fcreatechat.o
+93
View File
@@ -1571,3 +1571,96 @@ bool uDataBase::clearActionsTable()
query.exec("DELETE FROM sqlite_sequence WHERE name='actions'");
return true;
}
bool uDataBase::createDonationTriggersTable()
{
QSqlQuery query(m_db);
QString sql =
"CREATE TABLE IF NOT EXISTS donation_triggers ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" name TEXT NOT NULL,"
" rule TEXT NOT NULL,"
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
")";
if (!query.exec(sql)) {
m_lastError = query.lastError().text();
qWarning() << "Failed to create donation_triggers table:" << m_lastError;
return false;
}
return true;
}
int uDataBase::saveDonationTrigger(const DonationTrigger &trigger)
{
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return -1;
}
if (!tableExists("donation_triggers")) {
if (!createDonationTriggersTable()) return -1;
}
QSqlQuery query(m_db);
query.prepare("INSERT INTO donation_triggers (name, rule) VALUES (:name, :rule)");
query.bindValue(":name", trigger.name);
query.bindValue(":rule", trigger.rule);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to save donation trigger:" << m_lastError;
return -1;
}
return query.lastInsertId().toInt();
}
bool uDataBase::deleteDonationTrigger(int id)
{
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return false;
}
QSqlQuery query(m_db);
query.prepare("DELETE FROM donation_triggers WHERE id = :id");
query.bindValue(":id", id);
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to delete donation trigger:" << m_lastError;
return false;
}
return true;
}
QList<DonationTrigger> uDataBase::loadAllDonationTriggers()
{
QList<DonationTrigger> list;
if (!m_db.isOpen()) {
m_lastError = "Database is not open";
return list;
}
if (!tableExists("donation_triggers")) {
return list; // таблицы нет – пусто
}
QSqlQuery query(m_db);
query.prepare("SELECT id, name, rule FROM donation_triggers ORDER BY id");
if (!query.exec()) {
m_lastError = query.lastError().text();
qWarning() << "Failed to load donation triggers:" << m_lastError;
return list;
}
while (query.next()) {
DonationTrigger t;
t.id = query.value("id").toInt();
t.name = query.value("name").toString();
t.rule = query.value("rule").toString();
// Парсить rule будем в DonationManager, здесь только храним
list.append(t);
}
return list;
}
+5
View File
@@ -1,6 +1,7 @@
#ifndef UDATABASE_H
#define UDATABASE_H
#include "donationmanager.h"
#include "qlistwidget.h"
#include <QObject>
#include <QString>
@@ -137,6 +138,10 @@ public:
QList<ActionData> loadAllActions();
bool clearActionsTable();
bool createDonationTriggersTable();
int saveDonationTrigger(const DonationTrigger &trigger);
bool deleteDonationTrigger(int id);
QList<DonationTrigger> loadAllDonationTriggers();
private:
+67
View File
@@ -365,6 +365,14 @@ void uGeneral::setupTables()
this, &uGeneral::onFilesGridDoubleClicked);
connect(ui->widget_3->tableWidget(), &QTableWidget::cellDoubleClicked,
this, &uGeneral::onNeiroGridDoubleClicked);
ui->sgDotateTriggers->setColumnCount(2);
headers.clear();
headers << "Название" << "Правило";
ui->sgDotateTriggers->setHorizontalHeaderLabels(headers);
ui->sgDotateTriggers->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->sgDotateTriggers->setSelectionMode(QAbstractItemView::SingleSelection);
}
void uGeneral::initializeManagers()
@@ -468,6 +476,11 @@ void uGeneral::initializeManagers()
// Загружаем действия из БД
m_actionManager->loadFromDatabase();
updateActionsTable();
m_donationManager = new DonationManager(db, this);
connect(m_donationManager, &DonationManager::dataChanged, this, &uGeneral::updateDonationTriggersTable);
m_donationManager->loadFromDatabase();
updateDonationTriggersTable();
}
@@ -3719,3 +3732,57 @@ void uGeneral::clearActionInputs()
ui->edtActionPic->clear();
ui->edtActionSound->clear();
}
void uGeneral::updateDonationTriggersTable()
{
ui->sgDotateTriggers->setRowCount(0);
auto triggers = m_donationManager->getAllTriggers();
for (const auto &t : triggers) {
int row = ui->sgDotateTriggers->rowCount();
ui->sgDotateTriggers->insertRow(row);
ui->sgDotateTriggers->setItem(row, 0, new QTableWidgetItem(t.name));
ui->sgDotateTriggers->setItem(row, 1, new QTableWidgetItem(t.rule));
// Сохраним id в UserRole первого столбца для удаления
ui->sgDotateTriggers->item(row, 0)->setData(Qt::UserRole, t.id);
}
}
void uGeneral::on_btnDonateAdd_clicked()
{
QString name = ui->lineEdit->text().trimmed(); // поле для имени
QString rule = ui->lineEdit_2->text().trimmed(); // поле для правила
if (name.isEmpty() || rule.isEmpty()) {
QMessageBox::warning(this, "Ошибка", "Заполните оба поля!");
return;
}
if (!m_donationManager->addTrigger(name, rule)) {
QMessageBox::critical(this, "Ошибка", "Не удалось добавить триггер. Проверьте формат правила.");
} else {
ui->lineEdit->clear();
ui->lineEdit_2->clear();
}
}
void uGeneral::on_btnDonateDel_clicked()
{
int row = ui->sgDotateTriggers->currentRow();
if (row < 0) {
QMessageBox::warning(this, "Ошибка", "Выберите триггер для удаления!");
return;
}
int id = ui->sgDotateTriggers->item(row, 0)->data(Qt::UserRole).toInt();
if (QMessageBox::question(this, "Подтверждение", "Удалить выбранный триггер?") == QMessageBox::Yes) {
m_donationManager->deleteTrigger(id);
}
}
void uGeneral::on_sgDotateTriggers_cellDoubleClicked(int row, int column)
{
Q_UNUSED(column);
QString name = ui->sgDotateTriggers->item(row, 0)->text();
QString rule = ui->sgDotateTriggers->item(row, 1)->text();
ui->lineEdit->setText(name);
ui->lineEdit_2->setText(rule);
}
+9
View File
@@ -382,6 +382,11 @@ private slots:
void updateActionsTable();
void clearActionInputs();
void on_btnDonateAdd_clicked();
void on_btnDonateDel_clicked();
void on_sgDotateTriggers_cellDoubleClicked(int row, int column);
void updateDonationTriggersTable();
public slots:
// Установка статуса подключения к Twitch
void setTwitchConnected(bool connected);
@@ -408,6 +413,8 @@ private:
MediaFileManager *m_SoundFiles;
MediaFileManager *m_TextFiles;
NeuralTemplateManager *m_neuralTemplateManager;
DonationManager *m_donationManager;
QList<TimerInfo> m_timers; // Список таймеров
int m_nextTimerId = 1; // Следующий ID таймера
bool m_isTwitchConnected = false; // Статус подключения к Twitch
@@ -523,6 +530,8 @@ private:
void sendChatResponse(const QString &response);
QString cleanMessageFromAllEmotes(const QString& message) const;
};
#endif // UGENERAL_H
+84 -5
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>1002</width>
<height>985</height>
<height>838</height>
</rect>
</property>
<property name="windowTitle">
@@ -1449,14 +1449,74 @@
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer_8">
<layout class="QHBoxLayout" name="horizontalLayout_28">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_14">
<property name="title">
<string>Донаты</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_33">
<item>
<widget class="QTableWidget" name="sgDotateTriggers"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_29">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_36">
<property name="text">
<string>Название</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<widget class="QLabel" name="label_37">
<property name="text">
<string>Триггер</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_2"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_30">
<property name="topMargin">
<number>10</number>
</property>
<item>
<widget class="QPushButton" name="btnDonateAdd">
<property name="text">
<string>Добавить</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnDonateDel">
<property name="text">
<string>Удалить</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_13">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
@@ -1465,6 +1525,25 @@
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_15">
<property name="title">
<string>Связи</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_34">
<item>
<widget class="QTableWidget" name="tableWidget_2"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_5">
<property name="icon" stdset="0">
<iconset>