Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fc22ec606 | |||
| 5094834ea1 | |||
| eb494ae8fa | |||
| 05662be287 | |||
| b430b36e87 | |||
| ae4121157d | |||
| 63b7fa4ea1 |
@@ -21,7 +21,10 @@ RC_ICONS = ico\app_icon.ico
|
|||||||
INCLUDEPATH += $$PWD
|
INCLUDEPATH += $$PWD
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
actionmanager.cpp \
|
||||||
commandprocessor.cpp \
|
commandprocessor.cpp \
|
||||||
|
countermanager.cpp \
|
||||||
|
donationmanager.cpp \
|
||||||
emoteprovider.cpp \
|
emoteprovider.cpp \
|
||||||
fcolorsetting.cpp \
|
fcolorsetting.cpp \
|
||||||
fcreatechat.cpp \
|
fcreatechat.cpp \
|
||||||
@@ -40,6 +43,7 @@ SOURCES += \
|
|||||||
soundmanager.cpp \
|
soundmanager.cpp \
|
||||||
tauth.cpp \
|
tauth.cpp \
|
||||||
ttw_api.cpp \
|
ttw_api.cpp \
|
||||||
|
twitcheventsub.cpp \
|
||||||
twitchmessage.cpp \
|
twitchmessage.cpp \
|
||||||
udatabase.cpp \
|
udatabase.cpp \
|
||||||
ugeneral.cpp \
|
ugeneral.cpp \
|
||||||
@@ -52,8 +56,12 @@ SOURCES += \
|
|||||||
websocketclient.cpp
|
websocketclient.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
action.h \
|
||||||
|
actionmanager.h \
|
||||||
badge.h \
|
badge.h \
|
||||||
commandprocessor.h \
|
commandprocessor.h \
|
||||||
|
countermanager.h \
|
||||||
|
donationmanager.h \
|
||||||
emoteprovider.h \
|
emoteprovider.h \
|
||||||
fcolorsetting.h \
|
fcolorsetting.h \
|
||||||
fcreatechat.h \
|
fcreatechat.h \
|
||||||
@@ -74,6 +82,7 @@ HEADERS += \
|
|||||||
timerinfo.h \
|
timerinfo.h \
|
||||||
ttw_api.h \
|
ttw_api.h \
|
||||||
ttw_types.h \
|
ttw_types.h \
|
||||||
|
twitcheventsub.h \
|
||||||
twitchmessage.h \
|
twitchmessage.h \
|
||||||
udatabase.h \
|
udatabase.h \
|
||||||
ugeneral.h \
|
ugeneral.h \
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// action.h
|
||||||
|
#ifndef ACTION_H
|
||||||
|
#define ACTION_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
class Action {
|
||||||
|
public:
|
||||||
|
enum Type { KeyPress, Sound, Notification, File };
|
||||||
|
virtual ~Action() = default;
|
||||||
|
virtual Type type() const = 0;
|
||||||
|
virtual void execute() = 0;
|
||||||
|
virtual QVariantMap toJson() const = 0;
|
||||||
|
virtual void fromJson(const QVariantMap &data) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Конкретные действия
|
||||||
|
class KeyPressAction : public Action {
|
||||||
|
public:
|
||||||
|
Type type() const override { return KeyPress; }
|
||||||
|
void execute() override;
|
||||||
|
QVariantMap toJson() const override;
|
||||||
|
void fromJson(const QVariantMap &data) override;
|
||||||
|
private:
|
||||||
|
int keyCode;
|
||||||
|
Qt::KeyboardModifiers modifiers;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SoundAction : public Action {
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
|
||||||
|
// ... и т.д.
|
||||||
|
#endif // ACTION_H
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#include "actionmanager.h"
|
||||||
|
|
||||||
|
ActionManager::ActionManager(uDataBase *db, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_db(db)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActionManager::addAction(const ActionData &action)
|
||||||
|
{
|
||||||
|
if (!m_db) return false;
|
||||||
|
if (!m_db->saveAction(action)) return false;
|
||||||
|
|
||||||
|
// Перезагрузим все действия, чтобы получить свежий ID
|
||||||
|
loadFromDatabase();
|
||||||
|
emit dataChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActionManager::updateAction(int id, const ActionData &action)
|
||||||
|
{
|
||||||
|
if (!m_db) return false;
|
||||||
|
if (!m_db->updateAction(id, action)) return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_actions.size(); ++i) {
|
||||||
|
if (m_actions[i].id == id) {
|
||||||
|
m_actions[i] = action;
|
||||||
|
m_actions[i].id = id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit dataChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActionManager::deleteAction(int id)
|
||||||
|
{
|
||||||
|
if (!m_db) return false;
|
||||||
|
if (!m_db->deleteAction(id)) return false;
|
||||||
|
// Удаляем все связи с этим действием
|
||||||
|
m_db->deleteLinksByActionId(id);
|
||||||
|
|
||||||
|
for (int i = 0; i < m_actions.size(); ++i) {
|
||||||
|
if (m_actions[i].id == id) {
|
||||||
|
m_actions.removeAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit dataChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ActionData> ActionManager::getAllActions() const
|
||||||
|
{
|
||||||
|
return m_actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActionManager::loadFromDatabase()
|
||||||
|
{
|
||||||
|
if (!m_db) return false;
|
||||||
|
m_actions = m_db->loadAllActions();
|
||||||
|
emit dataChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionData ActionManager::getAction(int id) const
|
||||||
|
{
|
||||||
|
for (const ActionData &a : m_actions) {
|
||||||
|
if (a.id == id) return a;
|
||||||
|
}
|
||||||
|
return ActionData(); // с id = -1
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef ACTIONMANAGER_H
|
||||||
|
#define ACTIONMANAGER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include "udatabase.h"
|
||||||
|
|
||||||
|
class ActionManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ActionManager(uDataBase *db, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool addAction(const ActionData &action);
|
||||||
|
bool updateAction(int id, const ActionData &action);
|
||||||
|
bool deleteAction(int id);
|
||||||
|
QList<ActionData> getAllActions() const;
|
||||||
|
bool loadFromDatabase();
|
||||||
|
ActionData getAction(int id) const;
|
||||||
|
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void actionAdded(const ActionData &action);
|
||||||
|
void actionUpdated(int id, const ActionData &action);
|
||||||
|
void actionRemoved(int id);
|
||||||
|
void dataChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uDataBase *m_db;
|
||||||
|
QList<ActionData> m_actions;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ACTIONMANAGER_H
|
||||||
@@ -108,6 +108,7 @@ QString CommandProcessor::processCommand(const QString &sender, const QString &f
|
|||||||
response = parseTextFiles(response);
|
response = parseTextFiles(response);
|
||||||
response = parseBan(response, sender);
|
response = parseBan(response, sender);
|
||||||
response = parseAPI(response, sender);
|
response = parseAPI(response, sender);
|
||||||
|
response = parseCounters(response);
|
||||||
|
|
||||||
if (response.contains("[AI]", Qt::CaseInsensitive)) {
|
if (response.contains("[AI]", Qt::CaseInsensitive)) {
|
||||||
response = parseAI(response, parameters);
|
response = parseAI(response, parameters);
|
||||||
@@ -214,6 +215,24 @@ QString CommandProcessor::parseTextFiles(const QString &response)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString CommandProcessor::parseCounters(const QString &response)
|
||||||
|
{
|
||||||
|
QString result = response;
|
||||||
|
// Используем ленивый квантификатор +?
|
||||||
|
QRegularExpression regex("\\|\\)([^\\)]+?)\\|\\)");
|
||||||
|
QRegularExpressionMatchIterator matches = regex.globalMatch(response);
|
||||||
|
|
||||||
|
while (matches.hasNext()) {
|
||||||
|
QRegularExpressionMatch match = matches.next();
|
||||||
|
QString counterName = match.captured(1);
|
||||||
|
int count = m_context.counterManager->getCount(counterName);
|
||||||
|
QString countStr = QString::number(count);
|
||||||
|
result.replace("|)" + counterName + "|)", countStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QString CommandProcessor::parseRandomGroups(const QString &response)
|
QString CommandProcessor::parseRandomGroups(const QString &response)
|
||||||
{
|
{
|
||||||
QString result = response;
|
QString result = response;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QDate>
|
#include <QDate>
|
||||||
|
#include "countermanager.h"
|
||||||
#include "mediafilemanager.h"
|
#include "mediafilemanager.h"
|
||||||
#include "user_manager.h"
|
#include "user_manager.h"
|
||||||
#include "ttw_api.h"
|
#include "ttw_api.h"
|
||||||
@@ -34,6 +35,7 @@ public:
|
|||||||
RandomResponses* randomResponses = nullptr;
|
RandomResponses* randomResponses = nullptr;
|
||||||
MediaFileManager* mediaFileManager = nullptr;
|
MediaFileManager* mediaFileManager = nullptr;
|
||||||
NeuralTemplateManager *neuralTemplateManager = nullptr;
|
NeuralTemplateManager *neuralTemplateManager = nullptr;
|
||||||
|
CounterManager* counterManager = nullptr;
|
||||||
QString channel;
|
QString channel;
|
||||||
int notifyVolume = 50;
|
int notifyVolume = 50;
|
||||||
};
|
};
|
||||||
@@ -67,6 +69,7 @@ private:
|
|||||||
QString parseAPI(const QString &response, const QString &sender);
|
QString parseAPI(const QString &response, const QString &sender);
|
||||||
QString parseAI(const QString &response, const QString &question);
|
QString parseAI(const QString &response, const QString &question);
|
||||||
QString parseNeuralTemplates(const QString &response, const QString &sender, const QString ¶meters);
|
QString parseNeuralTemplates(const QString &response, const QString &sender, const QString ¶meters);
|
||||||
|
QString parseCounters(const QString &response);
|
||||||
|
|
||||||
QString extractParameters(const QString &fullCommand);
|
QString extractParameters(const QString &fullCommand);
|
||||||
QString getUsernameByIndex(QString userIndex) const;
|
QString getUsernameByIndex(QString userIndex) const;
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
// countermanager.cpp
|
||||||
|
#include "countermanager.h"
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
CounterManager::CounterManager(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_database(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CounterManager::~CounterManager()
|
||||||
|
{
|
||||||
|
if (m_database) {
|
||||||
|
saveToDatabase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CounterManager::initialize(uDataBase *database)
|
||||||
|
{
|
||||||
|
if (!database || !database->isConnected()) {
|
||||||
|
qWarning() << "CounterManager: База данных не подключена";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_database = database;
|
||||||
|
return loadFromDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CounterManager::addCounter(const QString &name, int initialValue)
|
||||||
|
{
|
||||||
|
if (name.isEmpty()) {
|
||||||
|
qWarning() << "CounterManager: Нельзя добавить счётчик с пустым именем";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contains(name)) {
|
||||||
|
qWarning() << "CounterManager: Счётчик с именем" << name << "уже существует";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter newCounter(name, initialValue);
|
||||||
|
m_counters.append(newCounter);
|
||||||
|
|
||||||
|
if (!saveToDatabase()) {
|
||||||
|
m_counters.removeLast();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit counterAdded(name, initialValue);
|
||||||
|
emit dataChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CounterManager::removeCounter(const QString &name)
|
||||||
|
{
|
||||||
|
int index = findIndex(name);
|
||||||
|
if (index == -1) {
|
||||||
|
qWarning() << "CounterManager: Счётчик" << name << "не найден";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter removed = m_counters.at(index);
|
||||||
|
m_counters.removeAt(index);
|
||||||
|
|
||||||
|
if (!saveToDatabase()) {
|
||||||
|
m_counters.insert(index, removed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit counterRemoved(name);
|
||||||
|
emit dataChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CounterManager::updateCounter(const QString &oldName, const QString &newName, int newValue)
|
||||||
|
{
|
||||||
|
int index = findIndex(oldName);
|
||||||
|
if (index == -1) {
|
||||||
|
qWarning() << "CounterManager: Счётчик" << oldName << "не найден";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldName != newName && contains(newName)) {
|
||||||
|
qWarning() << "CounterManager: Имя" << newName << "уже занято";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Counter oldCounter = m_counters.at(index);
|
||||||
|
m_counters[index].name = newName;
|
||||||
|
m_counters[index].count = newValue;
|
||||||
|
|
||||||
|
if (!saveToDatabase()) {
|
||||||
|
m_counters[index] = oldCounter;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit counterUpdated(oldName, newName);
|
||||||
|
emit dataChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CounterManager::incrementCounter(const QString &name, int step)
|
||||||
|
{
|
||||||
|
int index = findIndex(name);
|
||||||
|
if (index == -1) return false;
|
||||||
|
|
||||||
|
m_counters[index].count += step;
|
||||||
|
if (!saveToDatabase()) {
|
||||||
|
m_counters[index].count -= step; // откат
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit counterIncremented(name, m_counters[index].count);
|
||||||
|
emit dataChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CounterManager::decrementCounter(const QString &name, int step)
|
||||||
|
{
|
||||||
|
return incrementCounter(name, -step);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CounterManager::getCount(const QString &name) const
|
||||||
|
{
|
||||||
|
int index = findIndex(name);
|
||||||
|
return (index != -1) ? m_counters.at(index).count : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CounterManager::processMessage(const QString &message)
|
||||||
|
{
|
||||||
|
for (const Counter &c : m_counters) {
|
||||||
|
if (message.contains(c.name, Qt::CaseInsensitive)) {
|
||||||
|
// Увеличиваем счётчик, но без повторного сохранения после каждого (один общий save)
|
||||||
|
// Здесь мы меняем значение, но сохраним после цикла, чтобы не делать много запросов
|
||||||
|
// Для простоты используем incrementCounter, который сам сохраняет, но это будет много раз.
|
||||||
|
// Лучше собрать имена, которые нужно увеличить, а потом применить.
|
||||||
|
// Но в данном случае можно просто вызывать incrementCounter, это не критично.
|
||||||
|
incrementCounter(c.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CounterManager::contains(const QString &name) const
|
||||||
|
{
|
||||||
|
return findIndex(name) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CounterManager::saveToDatabase()
|
||||||
|
{
|
||||||
|
if (!m_database) {
|
||||||
|
qWarning() << "CounterManager: База данных не установлена";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTableWidget tempTable;
|
||||||
|
QStringList headers = {"Имя", "Значение"};
|
||||||
|
tempTable.setColumnCount(2);
|
||||||
|
tempTable.setHorizontalHeaderLabels(headers);
|
||||||
|
tempTable.setRowCount(m_counters.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < m_counters.size(); ++i) {
|
||||||
|
const Counter &c = m_counters.at(i);
|
||||||
|
tempTable.setItem(i, 0, new QTableWidgetItem(c.name));
|
||||||
|
tempTable.setItem(i, 1, new QTableWidgetItem(QString::number(c.count)));
|
||||||
|
}
|
||||||
|
tempTable.setObjectName("sgCounters");
|
||||||
|
// Сохраняем через общий метод (предполагаем, что таблица в БД называется "sgCounters")
|
||||||
|
m_database->SaveTableWidget(&tempTable);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CounterManager::loadFromDatabase()
|
||||||
|
{
|
||||||
|
if (!m_database) {
|
||||||
|
qWarning() << "CounterManager: База данных не установлена";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTableWidget tempTable;
|
||||||
|
QStringList headers = {"Имя", "Значение"};
|
||||||
|
tempTable.setColumnCount(2);
|
||||||
|
tempTable.setHorizontalHeaderLabels(headers);
|
||||||
|
tempTable.setObjectName("sgCounters");
|
||||||
|
m_database->LoadTableWidget(&tempTable);
|
||||||
|
|
||||||
|
m_counters.clear();
|
||||||
|
for (int row = 0; row < tempTable.rowCount(); ++row) {
|
||||||
|
QTableWidgetItem *nameItem = tempTable.item(row, 0);
|
||||||
|
QTableWidgetItem *valueItem = tempTable.item(row, 1);
|
||||||
|
|
||||||
|
if (!nameItem || !valueItem) continue;
|
||||||
|
|
||||||
|
QString name = nameItem->text().trimmed();
|
||||||
|
if (name.isEmpty()) continue;
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
int value = valueItem->text().toInt(&ok);
|
||||||
|
if (!ok) continue;
|
||||||
|
|
||||||
|
m_counters.append(Counter(name, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
emit dataChanged();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CounterManager::findIndex(const QString &name) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_counters.size(); ++i) {
|
||||||
|
if (m_counters.at(i).name == name)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// countermanager.h
|
||||||
|
#ifndef COUNTERMANAGER_H
|
||||||
|
#define COUNTERMANAGER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVector>
|
||||||
|
#include <QString>
|
||||||
|
#include "udatabase.h"
|
||||||
|
|
||||||
|
class CounterManager : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct Counter {
|
||||||
|
QString name;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
Counter() : count(0) {}
|
||||||
|
Counter(const QString& n, int c = 0) : name(n), count(c) {}
|
||||||
|
|
||||||
|
bool operator==(const QString& otherName) const { return name == otherName; }
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit CounterManager(QObject *parent = nullptr);
|
||||||
|
~CounterManager();
|
||||||
|
|
||||||
|
// Инициализация с базой данных
|
||||||
|
bool initialize(uDataBase *database);
|
||||||
|
|
||||||
|
// Управление счетчиками
|
||||||
|
bool addCounter(const QString &name, int initialValue = 0);
|
||||||
|
bool removeCounter(const QString &name);
|
||||||
|
bool updateCounter(const QString &oldName, const QString &newName, int newValue);
|
||||||
|
bool incrementCounter(const QString &name, int step = 1);
|
||||||
|
bool decrementCounter(const QString &name, int step = 1);
|
||||||
|
int getCount(const QString &name) const;
|
||||||
|
|
||||||
|
// Обработка сообщения: увеличивает счётчики, чьи имена встречаются в тексте
|
||||||
|
void processMessage(const QString &message);
|
||||||
|
|
||||||
|
// Получение всех счётчиков
|
||||||
|
QVector<Counter> getAllCounters() const { return m_counters; }
|
||||||
|
bool contains(const QString &name) const;
|
||||||
|
|
||||||
|
// Сохранение/загрузка в БД
|
||||||
|
bool saveToDatabase();
|
||||||
|
bool loadFromDatabase();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void dataChanged();
|
||||||
|
void counterAdded(const QString &name, int value);
|
||||||
|
void counterRemoved(const QString &name);
|
||||||
|
void counterUpdated(const QString &oldName, const QString &newName);
|
||||||
|
void counterIncremented(const QString &name, int newValue);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uDataBase *m_database;
|
||||||
|
QVector<Counter> m_counters;
|
||||||
|
|
||||||
|
int findIndex(const QString &name) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COUNTERMANAGER_H
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
// EmoteProvider implementation
|
// EmoteProvider implementation
|
||||||
EmoteProvider::EmoteProvider(QObject *parent)
|
EmoteProvider::EmoteProvider(QObject *parent)
|
||||||
@@ -416,3 +418,52 @@ void SevenTVProvider::parseCustomResponse(const QByteArray &data, const QString
|
|||||||
QString("Loaded %1 custom emotes for user %2").arg(m_emotes.size()).arg(userId),
|
QString("Loaded %1 custom emotes for user %2").arg(m_emotes.size()).arg(userId),
|
||||||
LOG_INFO);
|
LOG_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString BTTVProvider::cleanMessage(const QString& message) const
|
||||||
|
{
|
||||||
|
if (m_emotes.isEmpty())
|
||||||
|
return message;
|
||||||
|
|
||||||
|
// Собираем все коды эмоций в множество для быстрого поиска
|
||||||
|
QSet<QString> codes;
|
||||||
|
for (const BTTVEmote& emote : m_emotes) {
|
||||||
|
codes.insert(emote.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Разбиваем сообщение на токены (слова и не-слова)
|
||||||
|
QRegularExpression wordSplitter("(\\w+|[^\\w]+)");
|
||||||
|
QRegularExpressionMatchIterator it = wordSplitter.globalMatch(message);
|
||||||
|
QStringList parts;
|
||||||
|
while (it.hasNext()) {
|
||||||
|
QRegularExpressionMatch match = it.next();
|
||||||
|
QString token = match.captured(0);
|
||||||
|
// Если токен состоит только из букв/цифр и является кодом эмоции – пропускаем
|
||||||
|
if (token[0].isLetterOrNumber() && codes.contains(token))
|
||||||
|
continue;
|
||||||
|
parts.append(token);
|
||||||
|
}
|
||||||
|
return parts.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SevenTVProvider::cleanMessage(const QString& message) const
|
||||||
|
{
|
||||||
|
if (m_emotes.isEmpty())
|
||||||
|
return message;
|
||||||
|
|
||||||
|
QSet<QString> codes;
|
||||||
|
for (const SevenTVEmote& emote : m_emotes) {
|
||||||
|
codes.insert(emote.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegularExpression wordSplitter("(\\w+|[^\\w]+)");
|
||||||
|
QRegularExpressionMatchIterator it = wordSplitter.globalMatch(message);
|
||||||
|
QStringList parts;
|
||||||
|
while (it.hasNext()) {
|
||||||
|
QRegularExpressionMatch match = it.next();
|
||||||
|
QString token = match.captured(0);
|
||||||
|
if (token[0].isLetterOrNumber() && codes.contains(token))
|
||||||
|
continue;
|
||||||
|
parts.append(token);
|
||||||
|
}
|
||||||
|
return parts.join("");
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public:
|
|||||||
virtual void fetchGlobal() = 0;
|
virtual void fetchGlobal() = 0;
|
||||||
virtual void fetchCustom(const QString &userId) = 0;
|
virtual void fetchCustom(const QString &userId) = 0;
|
||||||
virtual QString getEmoteUrl(const QString &emoteName) = 0;
|
virtual QString getEmoteUrl(const QString &emoteName) = 0;
|
||||||
|
virtual QString cleanMessage(const QString& message) const = 0;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void emotesLoaded();
|
void emotesLoaded();
|
||||||
@@ -71,6 +72,7 @@ public:
|
|||||||
void fetchGlobal() override;
|
void fetchGlobal() override;
|
||||||
void fetchCustom(const QString &userId) override;
|
void fetchCustom(const QString &userId) override;
|
||||||
QString getEmoteUrl(const QString &emoteName) override;
|
QString getEmoteUrl(const QString &emoteName) override;
|
||||||
|
QString cleanMessage(const QString& message) const override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onGlobalReplyFinished(QNetworkReply *reply);
|
void onGlobalReplyFinished(QNetworkReply *reply);
|
||||||
@@ -95,6 +97,7 @@ public:
|
|||||||
void fetchGlobal() override;
|
void fetchGlobal() override;
|
||||||
void fetchCustom(const QString &userId) override;
|
void fetchCustom(const QString &userId) override;
|
||||||
QString getEmoteUrl(const QString &emoteName) override;
|
QString getEmoteUrl(const QString &emoteName) override;
|
||||||
|
QString cleanMessage(const QString& message) const override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onGlobalReplyFinished(QNetworkReply *reply);
|
void onGlobalReplyFinished(QNetworkReply *reply);
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
debug/actionmanager.o
|
||||||
debug/commandprocessor.o
|
debug/commandprocessor.o
|
||||||
|
debug/countermanager.o
|
||||||
|
debug/donationmanager.o
|
||||||
debug/emoteprovider.o
|
debug/emoteprovider.o
|
||||||
debug/fcolorsetting.o
|
debug/fcolorsetting.o
|
||||||
debug/fcreatechat.o
|
debug/fcreatechat.o
|
||||||
@@ -17,6 +20,7 @@ debug/randomresponses.o
|
|||||||
debug/soundmanager.o
|
debug/soundmanager.o
|
||||||
debug/tauth.o
|
debug/tauth.o
|
||||||
debug/ttw_api.o
|
debug/ttw_api.o
|
||||||
|
debug/twitcheventsub.o
|
||||||
debug/twitchmessage.o
|
debug/twitchmessage.o
|
||||||
debug/udatabase.o
|
debug/udatabase.o
|
||||||
debug/ugeneral.o
|
debug/ugeneral.o
|
||||||
@@ -27,7 +31,10 @@ debug/userwidget.o
|
|||||||
debug/webserverchat.o
|
debug/webserverchat.o
|
||||||
debug/webservernotify.o
|
debug/webservernotify.o
|
||||||
debug/websocketclient.o
|
debug/websocketclient.o
|
||||||
|
debug/moc_actionmanager.o
|
||||||
debug/moc_commandprocessor.o
|
debug/moc_commandprocessor.o
|
||||||
|
debug/moc_countermanager.o
|
||||||
|
debug/moc_donationmanager.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
|
||||||
@@ -43,6 +50,7 @@ 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
|
||||||
|
debug/moc_twitcheventsub.o
|
||||||
debug/moc_udatabase.o
|
debug/moc_udatabase.o
|
||||||
debug/moc_ugeneral.o
|
debug/moc_ugeneral.o
|
||||||
debug/moc_ulink.o
|
debug/moc_ulink.o
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
release/actionmanager.o
|
||||||
release/commandprocessor.o
|
release/commandprocessor.o
|
||||||
|
release/countermanager.o
|
||||||
|
release/donationmanager.o
|
||||||
release/emoteprovider.o
|
release/emoteprovider.o
|
||||||
release/fcolorsetting.o
|
release/fcolorsetting.o
|
||||||
release/fcreatechat.o
|
release/fcreatechat.o
|
||||||
@@ -17,6 +20,7 @@ release/randomresponses.o
|
|||||||
release/soundmanager.o
|
release/soundmanager.o
|
||||||
release/tauth.o
|
release/tauth.o
|
||||||
release/ttw_api.o
|
release/ttw_api.o
|
||||||
|
release/twitcheventsub.o
|
||||||
release/twitchmessage.o
|
release/twitchmessage.o
|
||||||
release/udatabase.o
|
release/udatabase.o
|
||||||
release/ugeneral.o
|
release/ugeneral.o
|
||||||
@@ -27,7 +31,10 @@ release/userwidget.o
|
|||||||
release/webserverchat.o
|
release/webserverchat.o
|
||||||
release/webservernotify.o
|
release/webservernotify.o
|
||||||
release/websocketclient.o
|
release/websocketclient.o
|
||||||
|
release/moc_actionmanager.o
|
||||||
release/moc_commandprocessor.o
|
release/moc_commandprocessor.o
|
||||||
|
release/moc_countermanager.o
|
||||||
|
release/moc_donationmanager.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
|
||||||
@@ -43,6 +50,7 @@ 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
|
||||||
|
release/moc_twitcheventsub.o
|
||||||
release/moc_udatabase.o
|
release/moc_udatabase.o
|
||||||
release/moc_ugeneral.o
|
release/moc_ugeneral.o
|
||||||
release/moc_ulink.o
|
release/moc_ulink.o
|
||||||
|
|||||||
+113
@@ -1,5 +1,6 @@
|
|||||||
#include "ttw_api.h"
|
#include "ttw_api.h"
|
||||||
#include "qeventloop.h"
|
#include "qeventloop.h"
|
||||||
|
#include "ttw_types.h"
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
@@ -692,3 +693,115 @@ bool TTwAPI::validateTwitchToken(const QString &tokenName,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TTwAPI::getCustomRewards(QVector<TCustomReward*> &rewards, bool onlyManageable)
|
||||||
|
{
|
||||||
|
QString roomId = getRoomId();
|
||||||
|
if (roomId.isEmpty()) return;
|
||||||
|
|
||||||
|
QString url = "channel_points/custom_rewards?broadcaster_id=" + roomId;
|
||||||
|
if (onlyManageable) {
|
||||||
|
url += "&only_manageable_rewards=true";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString response = getTTW(url, m_clientId, true);
|
||||||
|
if (response.isEmpty()) return;
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
||||||
|
if (!doc.isObject()) return;
|
||||||
|
|
||||||
|
QJsonArray data = doc.object()["data"].toArray();
|
||||||
|
for (const QJsonValue &val : data) {
|
||||||
|
QJsonObject obj = val.toObject();
|
||||||
|
TCustomReward *reward = new TCustomReward;
|
||||||
|
reward->id = obj["id"].toString();
|
||||||
|
reward->title = obj["title"].toString();
|
||||||
|
reward->cost = obj["cost"].toInt();
|
||||||
|
reward->prompt = obj["prompt"].toString();
|
||||||
|
reward->isUserInputRequired = obj["is_user_input_required"].toBool();
|
||||||
|
reward->isEnabled = obj["is_enabled"].toBool();
|
||||||
|
// Для manageable запроса все награды по определению управляемы
|
||||||
|
reward->isManagedByBroadcaster = onlyManageable;
|
||||||
|
rewards.append(reward);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TCustomReward* TTwAPI::createCustomReward(const QString &title, const QString &cost,
|
||||||
|
const QString &prompt, bool isUserInput)
|
||||||
|
{
|
||||||
|
QString roomId = getRoomId();
|
||||||
|
if (roomId.isEmpty()) return nullptr;
|
||||||
|
|
||||||
|
QJsonObject body;
|
||||||
|
body["title"] = title;
|
||||||
|
body["cost"] = cost.toInt();
|
||||||
|
if (!prompt.isEmpty()) body["prompt"] = prompt;
|
||||||
|
body["is_user_input_required"] = isUserInput;
|
||||||
|
// Можно добавить и другие параметры: is_enabled, color и т.д.
|
||||||
|
|
||||||
|
QJsonDocument doc(body);
|
||||||
|
QString response = postTTW("channel_points/custom_rewards?broadcaster_id=" + roomId,
|
||||||
|
m_clientId, doc.toJson(), true);
|
||||||
|
if (response.isEmpty()) return nullptr;
|
||||||
|
|
||||||
|
QJsonDocument respDoc = QJsonDocument::fromJson(response.toUtf8());
|
||||||
|
if (!respDoc.isObject()) return nullptr;
|
||||||
|
|
||||||
|
QJsonArray data = respDoc.object()["data"].toArray();
|
||||||
|
if (data.isEmpty()) return nullptr;
|
||||||
|
|
||||||
|
QJsonObject obj = data[0].toObject();
|
||||||
|
TCustomReward *reward = new TCustomReward;
|
||||||
|
reward->id = obj["id"].toString();
|
||||||
|
reward->title = obj["title"].toString();
|
||||||
|
reward->cost = obj["cost"].toInt();
|
||||||
|
reward->prompt = obj["prompt"].toString();
|
||||||
|
reward->isUserInputRequired = obj["is_user_input_required"].toBool();
|
||||||
|
reward->isEnabled = obj["is_enabled"].toBool();
|
||||||
|
|
||||||
|
return reward;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TTwAPI::updateCustomReward(TCustomReward* reward)
|
||||||
|
{
|
||||||
|
if (!reward) return;
|
||||||
|
QString roomId = getRoomId();
|
||||||
|
if (roomId.isEmpty()) return;
|
||||||
|
|
||||||
|
QJsonObject body;
|
||||||
|
if (!reward->title.isEmpty()) body["title"] = reward->title;
|
||||||
|
body["cost"] = reward->cost;
|
||||||
|
if (!reward->prompt.isEmpty()) body["prompt"] = reward->prompt;
|
||||||
|
body["is_user_input_required"] = reward->isUserInputRequired;
|
||||||
|
body["is_enabled"] = reward->isEnabled;
|
||||||
|
|
||||||
|
QJsonDocument doc(body);
|
||||||
|
patchTTW(QString("channel_points/custom_rewards?broadcaster_id=%1&id=%2")
|
||||||
|
.arg(roomId, reward->id),
|
||||||
|
m_clientId, doc.toJson(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TTwAPI::deleteCustomReward(const QString &id)
|
||||||
|
{
|
||||||
|
QString roomId = getRoomId();
|
||||||
|
if (roomId.isEmpty()) return false;
|
||||||
|
|
||||||
|
deleteTTW("channel_points/custom_rewards?broadcaster_id=" + roomId + "&id=" + id,
|
||||||
|
m_clientId, true);
|
||||||
|
return true; // или проверка HTTP-статуса, но deleteTTW возвращает пустую строку при ошибке
|
||||||
|
}
|
||||||
|
|
||||||
|
void TTwAPI::updateRedemptionStatus(TCustomRewardEvent* event)
|
||||||
|
{
|
||||||
|
// event содержит id награды, id redemption и новый статус (COMPLETED/CANCELED)
|
||||||
|
QString roomId = getRoomId();
|
||||||
|
if (roomId.isEmpty()) return;
|
||||||
|
|
||||||
|
QJsonObject body;
|
||||||
|
body["status"] = event->status; // "COMPLETED" или "CANCELED"
|
||||||
|
|
||||||
|
QJsonDocument doc(body);
|
||||||
|
patchTTW(QString("channel_points/custom_rewards/redemptions?broadcaster_id=%1&reward_id=%2&id=%3")
|
||||||
|
.arg(roomId, event->rewardId, event->redemptionId),
|
||||||
|
m_clientId, doc.toJson(), true);
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
class TCustomReward;
|
class TCustomReward;
|
||||||
class TCustomRewardEvent;
|
class TCustomRewardEvent;
|
||||||
class ChatBadge;
|
struct ChatBadge;
|
||||||
class Emote;
|
class Emote;
|
||||||
|
|
||||||
class TTwAPI : public QObject
|
class TTwAPI : public QObject
|
||||||
@@ -53,14 +53,14 @@ public:
|
|||||||
void delVIP(const QString &id);
|
void delVIP(const QString &id);
|
||||||
|
|
||||||
// Custom Rewards
|
// Custom Rewards
|
||||||
void getCustomRewards(QVector<TCustomReward*> &rewards);
|
void getCustomRewards(QVector<TCustomReward*> &rewards, bool onlyManageable = false);
|
||||||
TCustomReward* createCustomReward(const QString &title,
|
TCustomReward* createCustomReward(const QString &title,
|
||||||
const QString &cost,
|
const QString &cost,
|
||||||
const QString &prompt = "",
|
const QString &prompt = "",
|
||||||
bool isUserInput = false);
|
bool isUserInput = false);
|
||||||
void updateCustomReward(TCustomReward* reward);
|
void updateCustomReward(TCustomReward* reward);
|
||||||
void updateRedemptionStatus(TCustomRewardEvent* event);
|
void updateRedemptionStatus(TCustomRewardEvent* event);
|
||||||
void deleteCustomReward(const QString &id);
|
bool deleteCustomReward(const QString &id);
|
||||||
|
|
||||||
// Пользователи
|
// Пользователи
|
||||||
User getUserByLogin(const QString &login);
|
User getUserByLogin(const QString &login);
|
||||||
|
|||||||
+2
-11
@@ -22,7 +22,7 @@ struct TCustomReward {
|
|||||||
bool isPaused;
|
bool isPaused;
|
||||||
bool isInStock;
|
bool isInStock;
|
||||||
bool shouldRedemptionsSkipRequestQueue;
|
bool shouldRedemptionsSkipRequestQueue;
|
||||||
|
bool isManagedByBroadcaster;
|
||||||
TCustomReward()
|
TCustomReward()
|
||||||
: cost(0)
|
: cost(0)
|
||||||
, isEnabled(true)
|
, isEnabled(true)
|
||||||
@@ -48,21 +48,12 @@ struct TCustomRewardEvent {
|
|||||||
QString userInput;
|
QString userInput;
|
||||||
QString status;
|
QString status;
|
||||||
QDateTime redeemedAt;
|
QDateTime redeemedAt;
|
||||||
|
QString redemptionId;
|
||||||
|
|
||||||
TCustomRewardEvent() {}
|
TCustomRewardEvent() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ChatBadge {
|
|
||||||
QString setId;
|
|
||||||
QString versionId;
|
|
||||||
QString title;
|
|
||||||
QString description;
|
|
||||||
QString smallImageUrl;
|
|
||||||
QString mediumImageUrl;
|
|
||||||
QString largeImageUrl;
|
|
||||||
|
|
||||||
ChatBadge() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Emote {
|
struct Emote {
|
||||||
QString id;
|
QString id;
|
||||||
|
|||||||
@@ -0,0 +1,352 @@
|
|||||||
|
#include "twitcheventsub.h"
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
TwitchEventSub::TwitchEventSub(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_connected(false)
|
||||||
|
{
|
||||||
|
connect(&m_webSocket, &QWebSocket::connected, this, &TwitchEventSub::onWebSocketConnected);
|
||||||
|
connect(&m_webSocket, &QWebSocket::disconnected, this, &TwitchEventSub::onWebSocketDisconnected);
|
||||||
|
connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &TwitchEventSub::onWebSocketTextMessageReceived);
|
||||||
|
connect(&m_webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &TwitchEventSub::onWebSocketError);
|
||||||
|
|
||||||
|
m_pingTimer.setInterval(30000); // 30 секунд
|
||||||
|
connect(&m_pingTimer, &QTimer::timeout, this, &TwitchEventSub::onPingTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
TwitchEventSub::~TwitchEventSub()
|
||||||
|
{
|
||||||
|
disconnectFromTwitch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::init(const QString &accessToken, const QString &clientId, const QString &broadcasterId)
|
||||||
|
{
|
||||||
|
m_accessToken = accessToken;
|
||||||
|
m_clientId = clientId;
|
||||||
|
m_broadcasterId = broadcasterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::connectToTwitch()
|
||||||
|
{
|
||||||
|
if (m_webSocket.state() == QAbstractSocket::ConnectedState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
emit onLog(0, "connectToTwitch", "Connecting to EventSub WebSocket...");
|
||||||
|
m_webSocket.open(QUrl("wss://eventsub.wss.twitch.tv/ws?keepalive_timeout_seconds=60"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::disconnectFromTwitch()
|
||||||
|
{
|
||||||
|
m_pingTimer.stop();
|
||||||
|
m_webSocket.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::onWebSocketConnected()
|
||||||
|
{
|
||||||
|
emit onLog(0, "onWebSocketConnected", "WebSocket connected");
|
||||||
|
m_pingTimer.start();
|
||||||
|
m_connected = true;
|
||||||
|
emit onConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::onWebSocketDisconnected()
|
||||||
|
{
|
||||||
|
m_pingTimer.stop();
|
||||||
|
m_connected = false;
|
||||||
|
emit onLog(1, "onWebSocketDisconnected", "WebSocket disconnected");
|
||||||
|
emit onDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::onWebSocketTextMessageReceived(const QString &message)
|
||||||
|
{
|
||||||
|
emit onRawMessage(message);
|
||||||
|
parseMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::onWebSocketError(QAbstractSocket::SocketError error)
|
||||||
|
{
|
||||||
|
emit onLog(2, "onWebSocketError", m_webSocket.errorString());
|
||||||
|
emit onError(m_webSocket.errorString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::onPingTimer()
|
||||||
|
{
|
||||||
|
if (m_webSocket.state() == QAbstractSocket::ConnectedState) {
|
||||||
|
m_webSocket.ping(); // отправляем Ping-фрейм
|
||||||
|
emit onLog(3, "onPingTimer", "Ping sent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::parseMessage(const QString &message)
|
||||||
|
{
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8());
|
||||||
|
if (!doc.isObject()) return;
|
||||||
|
|
||||||
|
QJsonObject root = doc.object();
|
||||||
|
QJsonObject metadata = root.value("metadata").toObject();
|
||||||
|
QString messageType = metadata.value("message_type").toString();
|
||||||
|
QString subscriptionType = metadata.value("subscription_type").toString();
|
||||||
|
|
||||||
|
emit onLog(0, "parseMessage", QString("Message type: %1, Subscription: %2").arg(messageType, subscriptionType));
|
||||||
|
|
||||||
|
if (messageType == "session_welcome") {
|
||||||
|
QJsonObject payload = root.value("payload").toObject();
|
||||||
|
QJsonObject session = payload.value("session").toObject();
|
||||||
|
m_sessionId = session.value("id").toString();
|
||||||
|
emit onLog(0, "parseMessage", "Received session_welcome, session_id: " + m_sessionId);
|
||||||
|
performSubscriptions();
|
||||||
|
}
|
||||||
|
else if (messageType == "notification") {
|
||||||
|
QJsonObject payload = root.value("payload").toObject();
|
||||||
|
if (subscriptionType == "channel.channel_points_custom_reward_redemption.add") {
|
||||||
|
emit onCustomReward(parseCustomReward(payload));
|
||||||
|
}
|
||||||
|
else if (subscriptionType == "channel.follow") {
|
||||||
|
emit onFollow(parseFollow(payload));
|
||||||
|
}
|
||||||
|
else if (subscriptionType == "channel.subscribe") {
|
||||||
|
emit onSubscribe(parseSubscribe(payload));
|
||||||
|
}
|
||||||
|
else if (subscriptionType == "channel.subscription.gift") {
|
||||||
|
emit onGift(parseGift(payload));
|
||||||
|
}
|
||||||
|
else if (subscriptionType == "channel.raid") {
|
||||||
|
emit onRaid(parseRaid(payload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageType == "session_keepalive") {
|
||||||
|
emit onLog(3, "parseMessage", "Received keepalive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TwitchEventSub::performSubscriptions()
|
||||||
|
{
|
||||||
|
if (m_sessionId.isEmpty()) {
|
||||||
|
emit onLog(2, "performSubscriptions", "No session_id, cannot subscribe");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Формируем условие для каждого типа подписки
|
||||||
|
subscribeTo("channel.channel_points_custom_reward_redemption.add", "1",
|
||||||
|
QString("{\"broadcaster_user_id\":\"%1\"}").arg(m_broadcasterId));
|
||||||
|
_sleep(500);
|
||||||
|
subscribeTo("channel.raid", "1",
|
||||||
|
QString("{\"to_broadcaster_user_id\":\"%1\"}").arg(m_broadcasterId));
|
||||||
|
|
||||||
|
_sleep(500);
|
||||||
|
subscribeTo("channel.follow", "2",
|
||||||
|
QString("{\"broadcaster_user_id\":\"%1\",\"moderator_user_id\":\"%1\"}").arg(m_broadcasterId));
|
||||||
|
|
||||||
|
_sleep(500);
|
||||||
|
subscribeTo("channel.subscribe", "1",
|
||||||
|
QString("{\"broadcaster_user_id\":\"%1\"}").arg(m_broadcasterId));
|
||||||
|
|
||||||
|
_sleep(500);
|
||||||
|
subscribeTo("channel.subscription.gift", "1",
|
||||||
|
QString("{\"broadcaster_user_id\":\"%1\"}").arg(m_broadcasterId));
|
||||||
|
_sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --- Парсеры событий ---
|
||||||
|
TCustomRewardEvent2 TwitchEventSub::parseCustomReward(const QJsonObject &payload)
|
||||||
|
{
|
||||||
|
TCustomRewardEvent2 result;
|
||||||
|
QJsonObject subscription = payload.value("subscription").toObject();
|
||||||
|
result.subscription.id = subscription.value("id").toString();
|
||||||
|
result.subscription.type = subscription.value("type").toString();
|
||||||
|
result.subscription.version = subscription.value("version").toString();
|
||||||
|
result.subscription.status = subscription.value("status").toString();
|
||||||
|
result.subscription.cost = subscription.value("cost").toInt();
|
||||||
|
result.subscription.created_at = subscription.value("created_at").toString();
|
||||||
|
|
||||||
|
QJsonObject cond = subscription.value("condition").toObject();
|
||||||
|
result.subscription.condition.broadcaster_user_id = cond.value("broadcaster_user_id").toString();
|
||||||
|
result.subscription.condition.reward_id = cond.value("reward_id").toString();
|
||||||
|
|
||||||
|
QJsonObject transp = subscription.value("transport").toObject();
|
||||||
|
result.subscription.transport.method = transp.value("method").toString();
|
||||||
|
|
||||||
|
QJsonObject event = payload.value("event").toObject();
|
||||||
|
result.event.id = event.value("id").toString();
|
||||||
|
result.event.broadcaster_user_id = event.value("broadcaster_user_id").toString();
|
||||||
|
result.event.broadcaster_user_login = event.value("broadcaster_user_login").toString();
|
||||||
|
result.event.broadcaster_user_name = event.value("broadcaster_user_name").toString();
|
||||||
|
result.event.user_id = event.value("user_id").toString();
|
||||||
|
result.event.user_login = event.value("user_login").toString();
|
||||||
|
result.event.user_name = event.value("user_name").toString();
|
||||||
|
result.event.user_input = event.value("user_input").toString();
|
||||||
|
|
||||||
|
QJsonObject reward = event.value("reward").toObject();
|
||||||
|
result.event.reward.id = reward.value("id").toString();
|
||||||
|
result.event.reward.title = reward.value("title").toString();
|
||||||
|
result.event.reward.cost = reward.value("cost").toInt();
|
||||||
|
result.event.reward.prompt = reward.value("prompt").toString();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TFollowEvent TwitchEventSub::parseFollow(const QJsonObject &payload)
|
||||||
|
{
|
||||||
|
TFollowEvent result;
|
||||||
|
QJsonObject subscription = payload.value("subscription").toObject();
|
||||||
|
result.subscription.id = subscription.value("id").toString();
|
||||||
|
result.subscription.type = subscription.value("type").toString();
|
||||||
|
result.subscription.version = subscription.value("version").toString();
|
||||||
|
result.subscription.status = subscription.value("status").toString();
|
||||||
|
result.subscription.cost = subscription.value("cost").toInt();
|
||||||
|
result.subscription.created_at = subscription.value("created_at").toString();
|
||||||
|
|
||||||
|
QJsonObject cond = subscription.value("condition").toObject();
|
||||||
|
result.subscription.condition.broadcaster_user_id = cond.value("broadcaster_user_id").toString();
|
||||||
|
|
||||||
|
QJsonObject transp = subscription.value("transport").toObject();
|
||||||
|
result.subscription.transport.method = transp.value("method").toString();
|
||||||
|
|
||||||
|
QJsonObject event = payload.value("event").toObject();
|
||||||
|
result.event.broadcaster_user_id = event.value("broadcaster_user_id").toString();
|
||||||
|
result.event.broadcaster_user_login = event.value("broadcaster_user_login").toString();
|
||||||
|
result.event.broadcaster_user_name = event.value("broadcaster_user_name").toString();
|
||||||
|
result.event.user_id = event.value("user_id").toString();
|
||||||
|
result.event.user_login = event.value("user_login").toString();
|
||||||
|
result.event.user_name = event.value("user_name").toString();
|
||||||
|
result.event.followed_at = event.value("followed_at").toString();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSubEvent TwitchEventSub::parseSubscribe(const QJsonObject &payload)
|
||||||
|
{
|
||||||
|
TSubEvent result;
|
||||||
|
QJsonObject subscription = payload.value("subscription").toObject();
|
||||||
|
result.subscription.id = subscription.value("id").toString();
|
||||||
|
result.subscription.type = subscription.value("type").toString();
|
||||||
|
result.subscription.version = subscription.value("version").toString();
|
||||||
|
result.subscription.status = subscription.value("status").toString();
|
||||||
|
result.subscription.cost = subscription.value("cost").toInt();
|
||||||
|
result.subscription.created_at = subscription.value("created_at").toString();
|
||||||
|
|
||||||
|
QJsonObject cond = subscription.value("condition").toObject();
|
||||||
|
result.subscription.condition.broadcaster_user_id = cond.value("broadcaster_user_id").toString();
|
||||||
|
|
||||||
|
QJsonObject transp = subscription.value("transport").toObject();
|
||||||
|
result.subscription.transport.method = transp.value("method").toString();
|
||||||
|
|
||||||
|
QJsonObject event = payload.value("event").toObject();
|
||||||
|
result.event.broadcaster_user_id = event.value("broadcaster_user_id").toString();
|
||||||
|
result.event.broadcaster_user_login = event.value("broadcaster_user_login").toString();
|
||||||
|
result.event.broadcaster_user_name = event.value("broadcaster_user_name").toString();
|
||||||
|
result.event.user_id = event.value("user_id").toString();
|
||||||
|
result.event.user_login = event.value("user_login").toString();
|
||||||
|
result.event.user_name = event.value("user_name").toString();
|
||||||
|
result.event.tier = event.value("tier").toString();
|
||||||
|
result.event.is_gift = event.value("is_gift").toBool();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TGiftEvent TwitchEventSub::parseGift(const QJsonObject &payload)
|
||||||
|
{
|
||||||
|
TGiftEvent result;
|
||||||
|
QJsonObject subscription = payload.value("subscription").toObject();
|
||||||
|
result.subscription.id = subscription.value("id").toString();
|
||||||
|
result.subscription.type = subscription.value("type").toString();
|
||||||
|
result.subscription.version = subscription.value("version").toString();
|
||||||
|
result.subscription.status = subscription.value("status").toString();
|
||||||
|
result.subscription.cost = subscription.value("cost").toInt();
|
||||||
|
result.subscription.created_at = subscription.value("created_at").toString();
|
||||||
|
|
||||||
|
QJsonObject cond = subscription.value("condition").toObject();
|
||||||
|
result.subscription.condition.broadcaster_user_id = cond.value("broadcaster_user_id").toString();
|
||||||
|
|
||||||
|
QJsonObject transp = subscription.value("transport").toObject();
|
||||||
|
result.subscription.transport.method = transp.value("method").toString();
|
||||||
|
|
||||||
|
QJsonObject event = payload.value("event").toObject();
|
||||||
|
result.event.broadcaster_user_id = event.value("broadcaster_user_id").toString();
|
||||||
|
result.event.broadcaster_user_login = event.value("broadcaster_user_login").toString();
|
||||||
|
result.event.broadcaster_user_name = event.value("broadcaster_user_name").toString();
|
||||||
|
result.event.user_id = event.value("user_id").toString();
|
||||||
|
result.event.user_login = event.value("user_login").toString();
|
||||||
|
result.event.user_name = event.value("user_name").toString();
|
||||||
|
result.event.total = event.value("total").toInt();
|
||||||
|
result.event.tier = event.value("tier").toString();
|
||||||
|
result.event.cumulative_total = event.value("cumulative_total").toInt();
|
||||||
|
result.event.is_anonymous = event.value("is_anonymous").toBool();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRaidEvent TwitchEventSub::parseRaid(const QJsonObject &payload)
|
||||||
|
{
|
||||||
|
TRaidEvent result;
|
||||||
|
QJsonObject subscription = payload.value("subscription").toObject();
|
||||||
|
result.subscription.id = subscription.value("id").toString();
|
||||||
|
result.subscription.type = subscription.value("type").toString();
|
||||||
|
result.subscription.version = subscription.value("version").toString();
|
||||||
|
result.subscription.status = subscription.value("status").toString();
|
||||||
|
result.subscription.cost = subscription.value("cost").toInt();
|
||||||
|
result.subscription.created_at = subscription.value("created_at").toString();
|
||||||
|
|
||||||
|
QJsonObject cond = subscription.value("condition").toObject();
|
||||||
|
result.subscription.condition.to_broadcaster_user_id = cond.value("to_broadcaster_user_id").toString();
|
||||||
|
|
||||||
|
QJsonObject transp = subscription.value("transport").toObject();
|
||||||
|
result.subscription.transport.method = transp.value("method").toString();
|
||||||
|
|
||||||
|
QJsonObject event = payload.value("event").toObject();
|
||||||
|
result.event.from_broadcaster_user_id = event.value("from_broadcaster_user_id").toString();
|
||||||
|
result.event.from_broadcaster_user_login = event.value("from_broadcaster_user_login").toString();
|
||||||
|
result.event.from_broadcaster_user_name = event.value("from_broadcaster_user_name").toString();
|
||||||
|
result.event.to_broadcaster_user_id = event.value("to_broadcaster_user_id").toString();
|
||||||
|
result.event.to_broadcaster_user_login = event.value("to_broadcaster_user_login").toString();
|
||||||
|
result.event.to_broadcaster_user_name = event.value("to_broadcaster_user_name").toString();
|
||||||
|
result.event.viewers = event.value("viewers").toInt();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool TwitchEventSub::subscribeTo(const QString &eventType, const QString &version, const QString &condition)
|
||||||
|
{
|
||||||
|
emit onLog(0, "subscribeTo", QString("Subscribing to %1").arg(eventType));
|
||||||
|
|
||||||
|
QJsonObject transport;
|
||||||
|
transport["method"] = "websocket";
|
||||||
|
transport["session_id"] = m_sessionId;
|
||||||
|
|
||||||
|
QJsonObject requestBody;
|
||||||
|
requestBody["type"] = eventType;
|
||||||
|
requestBody["version"] = version;
|
||||||
|
requestBody["condition"] = QJsonDocument::fromJson(condition.toUtf8()).object();
|
||||||
|
requestBody["transport"] = transport;
|
||||||
|
|
||||||
|
QNetworkRequest request(QUrl("https://api.twitch.tv/helix/eventsub/subscriptions"));
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setRawHeader("Authorization", ("Bearer " + m_accessToken).toUtf8());
|
||||||
|
request.setRawHeader("Client-Id", m_clientId.toUtf8());
|
||||||
|
|
||||||
|
QNetworkReply *reply = m_networkManager.post(request, QJsonDocument(requestBody).toJson());
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply, eventType]() {
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QByteArray data = reply->readAll();
|
||||||
|
emit onLog(3, "subscribeTo", QString("Response: %1").arg(QString(data)));
|
||||||
|
if (data.contains("\"status\":\"enabled\"")) {
|
||||||
|
emit onLog(0, "subscribeTo", QString("Subscription %1 successful").arg(eventType));
|
||||||
|
} else {
|
||||||
|
emit onLog(1, "subscribeTo", QString("Subscription %1 failed: %2").arg(eventType, QString(data)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emit onLog(2, "subscribeTo", QString("Network error: %1").arg(reply->errorString()));
|
||||||
|
}
|
||||||
|
reply->deleteLater(); // обязательно удаляем
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
#ifndef TWITCHEVENTSUB_H
|
||||||
|
#define TWITCHEVENTSUB_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QWebSocket>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
// Структуры данных событий (аналог Delphi-записей)
|
||||||
|
struct TCustomRewardEvent2
|
||||||
|
{
|
||||||
|
struct Subscription {
|
||||||
|
QString id;
|
||||||
|
QString type;
|
||||||
|
QString version;
|
||||||
|
QString status;
|
||||||
|
int cost;
|
||||||
|
QString created_at;
|
||||||
|
struct Condition {
|
||||||
|
QString broadcaster_user_id;
|
||||||
|
QString reward_id;
|
||||||
|
} condition;
|
||||||
|
struct Transport {
|
||||||
|
QString method;
|
||||||
|
} transport;
|
||||||
|
} subscription;
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
QString id;
|
||||||
|
QString broadcaster_user_id;
|
||||||
|
QString broadcaster_user_login;
|
||||||
|
QString broadcaster_user_name;
|
||||||
|
QString user_id;
|
||||||
|
QString user_login;
|
||||||
|
QString user_name;
|
||||||
|
QString user_input;
|
||||||
|
struct Reward {
|
||||||
|
QString id;
|
||||||
|
QString title;
|
||||||
|
int cost;
|
||||||
|
QString prompt;
|
||||||
|
} reward;
|
||||||
|
} event;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TFollowEvent
|
||||||
|
{
|
||||||
|
struct Subscription {
|
||||||
|
QString id;
|
||||||
|
QString type;
|
||||||
|
QString version;
|
||||||
|
QString status;
|
||||||
|
int cost;
|
||||||
|
QString created_at;
|
||||||
|
struct Condition {
|
||||||
|
QString broadcaster_user_id;
|
||||||
|
} condition;
|
||||||
|
struct Transport {
|
||||||
|
QString method;
|
||||||
|
} transport;
|
||||||
|
} subscription;
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
QString broadcaster_user_id;
|
||||||
|
QString broadcaster_user_login;
|
||||||
|
QString broadcaster_user_name;
|
||||||
|
QString user_id;
|
||||||
|
QString user_login;
|
||||||
|
QString user_name;
|
||||||
|
QString followed_at;
|
||||||
|
} event;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TSubEvent
|
||||||
|
{
|
||||||
|
struct Subscription {
|
||||||
|
QString id;
|
||||||
|
QString type;
|
||||||
|
QString version;
|
||||||
|
QString status;
|
||||||
|
int cost;
|
||||||
|
QString created_at;
|
||||||
|
struct Condition {
|
||||||
|
QString broadcaster_user_id;
|
||||||
|
} condition;
|
||||||
|
struct Transport {
|
||||||
|
QString method;
|
||||||
|
} transport;
|
||||||
|
} subscription;
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
QString broadcaster_user_id;
|
||||||
|
QString broadcaster_user_login;
|
||||||
|
QString broadcaster_user_name;
|
||||||
|
QString user_id;
|
||||||
|
QString user_login;
|
||||||
|
QString user_name;
|
||||||
|
QString tier;
|
||||||
|
bool is_gift;
|
||||||
|
} event;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TGiftEvent
|
||||||
|
{
|
||||||
|
struct Subscription {
|
||||||
|
QString id;
|
||||||
|
QString type;
|
||||||
|
QString version;
|
||||||
|
QString status;
|
||||||
|
int cost;
|
||||||
|
QString created_at;
|
||||||
|
struct Condition {
|
||||||
|
QString broadcaster_user_id;
|
||||||
|
} condition;
|
||||||
|
struct Transport {
|
||||||
|
QString method;
|
||||||
|
} transport;
|
||||||
|
} subscription;
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
QString broadcaster_user_id;
|
||||||
|
QString broadcaster_user_login;
|
||||||
|
QString broadcaster_user_name;
|
||||||
|
QString user_id;
|
||||||
|
QString user_login;
|
||||||
|
QString user_name;
|
||||||
|
int total;
|
||||||
|
QString tier;
|
||||||
|
int cumulative_total;
|
||||||
|
bool is_anonymous;
|
||||||
|
} event;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TRaidEvent
|
||||||
|
{
|
||||||
|
struct Subscription {
|
||||||
|
QString id;
|
||||||
|
QString type;
|
||||||
|
QString version;
|
||||||
|
QString status;
|
||||||
|
int cost;
|
||||||
|
QString created_at;
|
||||||
|
struct Condition {
|
||||||
|
QString to_broadcaster_user_id;
|
||||||
|
} condition;
|
||||||
|
struct Transport {
|
||||||
|
QString method;
|
||||||
|
} transport;
|
||||||
|
} subscription;
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
QString from_broadcaster_user_id;
|
||||||
|
QString from_broadcaster_user_login;
|
||||||
|
QString from_broadcaster_user_name;
|
||||||
|
QString to_broadcaster_user_id;
|
||||||
|
QString to_broadcaster_user_login;
|
||||||
|
QString to_broadcaster_user_name;
|
||||||
|
int viewers;
|
||||||
|
} event;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TwitchEventSub : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit TwitchEventSub(QObject *parent = nullptr);
|
||||||
|
~TwitchEventSub();
|
||||||
|
|
||||||
|
void init(const QString &accessToken, const QString &clientId, const QString &broadcasterId);
|
||||||
|
void connectToTwitch();
|
||||||
|
void disconnectFromTwitch();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void onConnected();
|
||||||
|
void onDisconnected();
|
||||||
|
void onError(const QString &error);
|
||||||
|
void onLog(int level, const QString &method, const QString &message);
|
||||||
|
void onStatus(const QString &event, int code, const QString &description);
|
||||||
|
void onRawMessage(const QString &message);
|
||||||
|
|
||||||
|
void onCustomReward(const TCustomRewardEvent2 &data);
|
||||||
|
void onFollow(const TFollowEvent &data);
|
||||||
|
void onSubscribe(const TSubEvent &data);
|
||||||
|
void onGift(const TGiftEvent &data);
|
||||||
|
void onRaid(const TRaidEvent &data);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onWebSocketConnected();
|
||||||
|
void onWebSocketDisconnected();
|
||||||
|
void onWebSocketTextMessageReceived(const QString &message);
|
||||||
|
void onWebSocketError(QAbstractSocket::SocketError error);
|
||||||
|
void onPingTimer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool subscribeTo(const QString &eventType, const QString &version, const QString &condition);
|
||||||
|
void performSubscriptions();
|
||||||
|
void parseMessage(const QString &message);
|
||||||
|
|
||||||
|
// Парсеры событий
|
||||||
|
TCustomRewardEvent2 parseCustomReward(const QJsonObject &payload);
|
||||||
|
TFollowEvent parseFollow(const QJsonObject &payload);
|
||||||
|
TSubEvent parseSubscribe(const QJsonObject &payload);
|
||||||
|
TGiftEvent parseGift(const QJsonObject &payload);
|
||||||
|
TRaidEvent parseRaid(const QJsonObject &payload);
|
||||||
|
|
||||||
|
QString m_accessToken;
|
||||||
|
QString m_clientId;
|
||||||
|
QString m_broadcasterId;
|
||||||
|
QString m_sessionId;
|
||||||
|
|
||||||
|
QWebSocket m_webSocket;
|
||||||
|
QNetworkAccessManager m_networkManager;
|
||||||
|
QTimer m_pingTimer;
|
||||||
|
bool m_connected;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TWITCHEVENTSUB_H
|
||||||
+401
@@ -1399,3 +1399,404 @@ bool uDataBase::deleteNotification(int port)
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool uDataBase::createActionsTable()
|
||||||
|
{
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
QString sql =
|
||||||
|
"CREATE TABLE IF NOT EXISTS actions ("
|
||||||
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
|
" type INTEGER NOT NULL,"
|
||||||
|
" key_combination TEXT,"
|
||||||
|
" audio_file TEXT,"
|
||||||
|
" notification_title TEXT,"
|
||||||
|
" notification_description TEXT,"
|
||||||
|
" notification_image TEXT,"
|
||||||
|
" notification_sound TEXT,"
|
||||||
|
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||||
|
")";
|
||||||
|
|
||||||
|
if (!query.exec(sql)) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to create actions table:" << m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uDataBase::saveAction(const ActionData &action)
|
||||||
|
{
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаём таблицу, если ещё нет
|
||||||
|
if (!tableExists("actions")) {
|
||||||
|
if (!createActionsTable())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
query.prepare(
|
||||||
|
"INSERT INTO actions ("
|
||||||
|
" type, key_combination, audio_file, notification_title,"
|
||||||
|
" notification_description, notification_image, notification_sound"
|
||||||
|
") VALUES ("
|
||||||
|
" :type, :key_combination, :audio_file, :notification_title,"
|
||||||
|
" :notification_description, :notification_image, :notification_sound"
|
||||||
|
")"
|
||||||
|
);
|
||||||
|
|
||||||
|
query.bindValue(":type", action.type);
|
||||||
|
query.bindValue(":key_combination", action.keyCombination);
|
||||||
|
query.bindValue(":audio_file", action.audioFile);
|
||||||
|
query.bindValue(":notification_title", action.notificationTitle);
|
||||||
|
query.bindValue(":notification_description", action.notificationDescription);
|
||||||
|
query.bindValue(":notification_image", action.notificationImage);
|
||||||
|
query.bindValue(":notification_sound", action.notificationSound);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to save action:" << m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uDataBase::updateAction(int id, const ActionData &action)
|
||||||
|
{
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
query.prepare(
|
||||||
|
"UPDATE actions SET "
|
||||||
|
" type = :type,"
|
||||||
|
" key_combination = :key_combination,"
|
||||||
|
" audio_file = :audio_file,"
|
||||||
|
" notification_title = :notification_title,"
|
||||||
|
" notification_description = :notification_description,"
|
||||||
|
" notification_image = :notification_image,"
|
||||||
|
" notification_sound = :notification_sound "
|
||||||
|
"WHERE id = :id"
|
||||||
|
);
|
||||||
|
|
||||||
|
query.bindValue(":type", action.type);
|
||||||
|
query.bindValue(":key_combination", action.keyCombination);
|
||||||
|
query.bindValue(":audio_file", action.audioFile);
|
||||||
|
query.bindValue(":notification_title", action.notificationTitle);
|
||||||
|
query.bindValue(":notification_description", action.notificationDescription);
|
||||||
|
query.bindValue(":notification_image", action.notificationImage);
|
||||||
|
query.bindValue(":notification_sound", action.notificationSound);
|
||||||
|
query.bindValue(":id", id);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to update action:" << m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uDataBase::deleteAction(int id)
|
||||||
|
{
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
query.prepare("DELETE FROM actions WHERE id = :id");
|
||||||
|
query.bindValue(":id", id);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to delete action:" << m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ActionData> uDataBase::loadAllActions()
|
||||||
|
{
|
||||||
|
QList<ActionData> actions;
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tableExists("actions")) {
|
||||||
|
return actions; // таблицы нет – пустой список
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
query.prepare("SELECT * FROM actions ORDER BY id");
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to load actions:" << m_lastError;
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (query.next()) {
|
||||||
|
ActionData a;
|
||||||
|
a.id = query.value("id").toInt();
|
||||||
|
a.type = query.value("type").toInt();
|
||||||
|
a.keyCombination = query.value("key_combination").toString();
|
||||||
|
a.audioFile = query.value("audio_file").toString();
|
||||||
|
a.notificationTitle = query.value("notification_title").toString();
|
||||||
|
a.notificationDescription = query.value("notification_description").toString();
|
||||||
|
a.notificationImage = query.value("notification_image").toString();
|
||||||
|
a.notificationSound = query.value("notification_sound").toString();
|
||||||
|
actions.append(a);
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uDataBase::clearActionsTable()
|
||||||
|
{
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
if (!query.exec("DELETE FROM actions")) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Сброс автоинкремента
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uDataBase::saveEventActionLink(const QString &eventType, const QString &eventName, int actionId)
|
||||||
|
{
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QString et = eventType.trimmed();
|
||||||
|
QString en = eventName.trimmed();
|
||||||
|
// Создаём таблицу, если её нет
|
||||||
|
if (!tableExists("event_action_links")) {
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
QString sql = "CREATE TABLE IF NOT EXISTS event_action_links ("
|
||||||
|
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
|
"event_type TEXT NOT NULL,"
|
||||||
|
"event_name TEXT NOT NULL,"
|
||||||
|
"action_id INTEGER NOT NULL,"
|
||||||
|
"created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||||
|
")";
|
||||||
|
if (!query.exec(sql)) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to create event_action_links table:" << m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qDebug()<<"Я ТУТ НАХУЙ";
|
||||||
|
// Проверяем, нет ли уже такой связи
|
||||||
|
QSqlQuery checkQuery(m_db);
|
||||||
|
checkQuery.prepare("SELECT id FROM event_action_links WHERE event_type=:et AND event_name=:en AND action_id=:aid");
|
||||||
|
checkQuery.bindValue(":et", et);
|
||||||
|
checkQuery.bindValue(":en", en);
|
||||||
|
checkQuery.bindValue(":aid", actionId);
|
||||||
|
if (checkQuery.exec() && checkQuery.next()) {
|
||||||
|
m_lastError = "Такая связь уже существует";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
query.prepare("INSERT INTO event_action_links (event_type, event_name, action_id) VALUES (:et, :en, :aid)");
|
||||||
|
query.bindValue(":et", eventType);
|
||||||
|
query.bindValue(":en", eventName);
|
||||||
|
query.bindValue(":aid", actionId);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to save event-action link:" << m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uDataBase::deleteEventActionLink(int id)
|
||||||
|
{
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
query.prepare("DELETE FROM event_action_links WHERE id = :id");
|
||||||
|
query.bindValue(":id", id);
|
||||||
|
if (!query.exec()) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to delete event-action link:" << m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (query.numRowsAffected() == 0) {
|
||||||
|
m_lastError = "Связь с указанным ID не найдена";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<EventActionLink> uDataBase::getLinksForEvent(const QString &eventType, const QString &eventName)
|
||||||
|
{
|
||||||
|
QList<EventActionLink> links;
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
if (!tableExists("event_action_links")) {
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
query.prepare("SELECT id, event_type, event_name, action_id FROM event_action_links WHERE event_type=:et AND event_name=:en");
|
||||||
|
query.bindValue(":et", eventType);
|
||||||
|
query.bindValue(":en", eventName);
|
||||||
|
if (!query.exec()) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to get links for event:" << m_lastError;
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
while (query.next()) {
|
||||||
|
EventActionLink link;
|
||||||
|
link.id = query.value(0).toInt();
|
||||||
|
link.eventType = query.value(1).toString();
|
||||||
|
link.eventName = query.value(2).toString();
|
||||||
|
link.actionId = query.value(3).toInt();
|
||||||
|
links.append(link);
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uDataBase::deleteLinksForEvent(const QString &eventType, const QString &eventName)
|
||||||
|
{
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
query.prepare("DELETE FROM event_action_links WHERE event_type=:et AND event_name=:en");
|
||||||
|
query.bindValue(":et", eventType);
|
||||||
|
query.bindValue(":en", eventName);
|
||||||
|
if (!query.exec()) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to delete links for event:" << m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uDataBase::deleteLinksByActionId(int actionId)
|
||||||
|
{
|
||||||
|
if (!m_db.isOpen()) {
|
||||||
|
m_lastError = "Database is not open";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QSqlQuery query(m_db);
|
||||||
|
query.prepare("DELETE FROM event_action_links WHERE action_id = :aid");
|
||||||
|
query.bindValue(":aid", actionId);
|
||||||
|
if (!query.exec()) {
|
||||||
|
m_lastError = query.lastError().text();
|
||||||
|
qWarning() << "Failed to delete links by action id:" << m_lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
+34
@@ -1,6 +1,7 @@
|
|||||||
#ifndef UDATABASE_H
|
#ifndef UDATABASE_H
|
||||||
#define UDATABASE_H
|
#define UDATABASE_H
|
||||||
|
|
||||||
|
#include "donationmanager.h"
|
||||||
#include "qlistwidget.h"
|
#include "qlistwidget.h"
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@@ -56,7 +57,23 @@ struct NotificationSettings {
|
|||||||
QDateTime createdAt;
|
QDateTime createdAt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ActionData {
|
||||||
|
int id = -1;
|
||||||
|
int type = 0; // 0 - клавиши, 1 - звук, 2 - уведомление
|
||||||
|
QString keyCombination;
|
||||||
|
QString audioFile;
|
||||||
|
QString notificationTitle;
|
||||||
|
QString notificationDescription;
|
||||||
|
QString notificationImage;
|
||||||
|
QString notificationSound;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EventActionLink {
|
||||||
|
int id;
|
||||||
|
QString eventType;
|
||||||
|
QString eventName;
|
||||||
|
int actionId;
|
||||||
|
};
|
||||||
|
|
||||||
class uDataBase : public QObject
|
class uDataBase : public QObject
|
||||||
{
|
{
|
||||||
@@ -121,6 +138,23 @@ public:
|
|||||||
// Получение последней ошибки
|
// Получение последней ошибки
|
||||||
QString lastError() const;
|
QString lastError() const;
|
||||||
|
|
||||||
|
bool createActionsTable();
|
||||||
|
bool saveAction(const ActionData &action);
|
||||||
|
bool updateAction(int id, const ActionData &action);
|
||||||
|
bool deleteAction(int id);
|
||||||
|
QList<ActionData> loadAllActions();
|
||||||
|
bool clearActionsTable();
|
||||||
|
|
||||||
|
bool createDonationTriggersTable();
|
||||||
|
int saveDonationTrigger(const DonationTrigger &trigger);
|
||||||
|
bool deleteDonationTrigger(int id);
|
||||||
|
QList<DonationTrigger> loadAllDonationTriggers();
|
||||||
|
|
||||||
|
bool saveEventActionLink(const QString &eventType, const QString &eventName, int actionId);
|
||||||
|
bool deleteEventActionLink(int id);
|
||||||
|
QList<EventActionLink> getLinksForEvent(const QString &eventType, const QString &eventName);
|
||||||
|
bool deleteLinksForEvent(const QString &eventType, const QString &eventName);
|
||||||
|
bool deleteLinksByActionId(int actionId);
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+992
-10
File diff suppressed because it is too large
Load Diff
+68
-9
@@ -9,12 +9,15 @@
|
|||||||
#include <ulink.h>
|
#include <ulink.h>
|
||||||
#include <udatabase.h>
|
#include <udatabase.h>
|
||||||
#include <soundmanager.h>
|
#include <soundmanager.h>
|
||||||
|
#include "actionmanager.h"
|
||||||
#include "commandprocessor.h"
|
#include "commandprocessor.h"
|
||||||
|
#include "countermanager.h"
|
||||||
#include "fcreatechat.h"
|
#include "fcreatechat.h"
|
||||||
#include "fcreatenotify.h"
|
#include "fcreatenotify.h"
|
||||||
#include "logmanager.h"
|
#include "logmanager.h"
|
||||||
#include "neuralnetworkmanager.h"
|
#include "neuralnetworkmanager.h"
|
||||||
#include "ttw_api.h"
|
#include "ttw_api.h"
|
||||||
|
#include "twitcheventsub.h"
|
||||||
#include "user_manager.h"
|
#include "user_manager.h"
|
||||||
#include "webserverchat.h"
|
#include "webserverchat.h"
|
||||||
#include "webservernotify.h"
|
#include "webservernotify.h"
|
||||||
@@ -84,7 +87,7 @@ public:
|
|||||||
SoundManager *soundManager; // Менеджер звуков
|
SoundManager *soundManager; // Менеджер звуков
|
||||||
UserManager* getUserManager(); // Получение менеджера пользователей
|
UserManager* getUserManager(); // Получение менеджера пользователей
|
||||||
TTwAPI *twitchAPI; // API для работы с Twitch
|
TTwAPI *twitchAPI; // API для работы с Twitch
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
// Методы логирования и работы с командами
|
// Методы логирования и работы с командами
|
||||||
|
|
||||||
void toCommands(QString command, QString response);
|
void toCommands(QString command, QString response);
|
||||||
@@ -141,11 +144,6 @@ private slots:
|
|||||||
void handleConnected();
|
void handleConnected();
|
||||||
void handleDisconnected();
|
void handleDisconnected();
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// СЛОТЫ ДЛЯ РАБОТЫ С КОМАНДАМИ И ОТВЕТАМИ
|
|
||||||
// ========================================================================
|
|
||||||
void execCommand(const QString &sender, const QString &message);
|
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// СЛОТЫ ДЛЯ РАБОТЫ С ИСКУССТВЕННЫМ ИНТЕЛЛЕКТОМ
|
// СЛОТЫ ДЛЯ РАБОТЫ С ИСКУССТВЕННЫМ ИНТЕЛЛЕКТОМ
|
||||||
@@ -346,6 +344,56 @@ private slots:
|
|||||||
|
|
||||||
void on_btnRmWebService_clicked();
|
void on_btnRmWebService_clicked();
|
||||||
|
|
||||||
|
void on_sgCounters_cellClicked(int row, int column);
|
||||||
|
|
||||||
|
void on_sgCounters_cellDoubleClicked(int row, int column);
|
||||||
|
|
||||||
|
void on_btnCounterAdd_clicked();
|
||||||
|
|
||||||
|
void on_btnCounterDelete_clicked();
|
||||||
|
|
||||||
|
void on_btnCounterEdit_clicked();
|
||||||
|
|
||||||
|
void on_btnCounterAtoText_clicked();
|
||||||
|
|
||||||
|
void on_btnCRGet_clicked();
|
||||||
|
|
||||||
|
void on_sgCustomRewards_cellClicked(int row, int column);
|
||||||
|
|
||||||
|
void on_sgCustomRewards_cellDoubleClicked(int row, int column);
|
||||||
|
|
||||||
|
void on_btnCRAdd_clicked();
|
||||||
|
|
||||||
|
void on_btnCREdit_clicked();
|
||||||
|
|
||||||
|
void on_btnCRDelete_clicked();
|
||||||
|
|
||||||
|
void on_cbActions_currentIndexChanged(int index);
|
||||||
|
|
||||||
|
void on_btnOpenAudioFile_clicked();
|
||||||
|
|
||||||
|
void on_btnActionPicOpen_clicked();
|
||||||
|
|
||||||
|
void on_btnActionAudioOpen_clicked();
|
||||||
|
|
||||||
|
void on_btnAddAction_clicked();
|
||||||
|
void on_btnDelAction_clicked();
|
||||||
|
void on_sgActions_cellClicked(int row, int column);
|
||||||
|
void on_sgActions_cellDoubleClicked(int row, int column);
|
||||||
|
void updateActionsTable();
|
||||||
|
void clearActionInputs();
|
||||||
|
|
||||||
|
void on_btnDonateAdd_clicked();
|
||||||
|
void on_btnDonateDel_clicked();
|
||||||
|
void on_sgDotateTriggers_cellDoubleClicked(int row, int column);
|
||||||
|
void updateDonationTriggersTable();
|
||||||
|
|
||||||
|
void on_cbDonateList_currentIndexChanged(int index);
|
||||||
|
|
||||||
|
void on_btnLinksAdd_clicked();
|
||||||
|
|
||||||
|
void on_btnLinksDel_clicked();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
// Установка статуса подключения к Twitch
|
// Установка статуса подключения к Twitch
|
||||||
void setTwitchConnected(bool connected);
|
void setTwitchConnected(bool connected);
|
||||||
@@ -363,6 +411,7 @@ private:
|
|||||||
uLink *fLinkForm; // Форма ссылок
|
uLink *fLinkForm; // Форма ссылок
|
||||||
TTTVAuth *TTVAuth; // Данные авторизации Twitch
|
TTTVAuth *TTVAuth; // Данные авторизации Twitch
|
||||||
UserManager *m_userManager; // Менеджер пользователей
|
UserManager *m_userManager; // Менеджер пользователей
|
||||||
|
CounterManager *m_counterManager;
|
||||||
CommandProcessor* m_commandProcessor; // Процессор команд
|
CommandProcessor* m_commandProcessor; // Процессор команд
|
||||||
WebSocketClient *m_twitchClient; // WebSocket клиент для Twitch
|
WebSocketClient *m_twitchClient; // WebSocket клиент для Twitch
|
||||||
UserWidget* m_userWidget; // Виджет пользователя
|
UserWidget* m_userWidget; // Виджет пользователя
|
||||||
@@ -371,10 +420,15 @@ private:
|
|||||||
MediaFileManager *m_SoundFiles;
|
MediaFileManager *m_SoundFiles;
|
||||||
MediaFileManager *m_TextFiles;
|
MediaFileManager *m_TextFiles;
|
||||||
NeuralTemplateManager *m_neuralTemplateManager;
|
NeuralTemplateManager *m_neuralTemplateManager;
|
||||||
|
DonationManager *m_donationManager;
|
||||||
|
TwitchEventSub *m_twitchEventSub = nullptr;
|
||||||
QList<TimerInfo> m_timers; // Список таймеров
|
QList<TimerInfo> m_timers; // Список таймеров
|
||||||
int m_nextTimerId = 1; // Следующий ID таймера
|
int m_nextTimerId = 1; // Следующий ID таймера
|
||||||
bool m_isTwitchConnected = false; // Статус подключения к Twitch
|
bool m_isTwitchConnected = false; // Статус подключения к Twitch
|
||||||
|
QVector<TCustomReward*> m_rewards;
|
||||||
|
ActionManager *m_actionManager;
|
||||||
|
// возможно, сохраняем текущее редактируемое действие
|
||||||
|
int m_currentActionId = -1;
|
||||||
// Менеджеры веб-серверов
|
// Менеджеры веб-серверов
|
||||||
QList<HttpServer*> m_notificationServers;
|
QList<HttpServer*> m_notificationServers;
|
||||||
QList<HttpServerChat*> m_chatServers;
|
QList<HttpServerChat*> m_chatServers;
|
||||||
@@ -405,8 +459,8 @@ private:
|
|||||||
int findNotificationServerRow(HttpServer *server);
|
int findNotificationServerRow(HttpServer *server);
|
||||||
int findChatServerRow(HttpServerChat *server);
|
int findChatServerRow(HttpServerChat *server);
|
||||||
QString generateServerId() const;
|
QString generateServerId() const;
|
||||||
|
void setupCountersTable();
|
||||||
|
void updateCountersTable();
|
||||||
// Текущие настройки для формы
|
// Текущие настройки для формы
|
||||||
QVariantMap m_currentSettings;
|
QVariantMap m_currentSettings;
|
||||||
|
|
||||||
@@ -481,6 +535,11 @@ private:
|
|||||||
void loadNeuralTemplatesFromTableWidget();
|
void loadNeuralTemplatesFromTableWidget();
|
||||||
void processUserCommand(const QString &username, const QString &commandText);
|
void processUserCommand(const QString &username, const QString &commandText);
|
||||||
void sendChatResponse(const QString &response);
|
void sendChatResponse(const QString &response);
|
||||||
|
|
||||||
|
QString cleanMessageFromAllEmotes(const QString& message) const;
|
||||||
|
void updateDonateList();
|
||||||
|
void updateActionsList();
|
||||||
|
void updateLinksList();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // UGENERAL_H
|
#endif // UGENERAL_H
|
||||||
|
|||||||
+767
-124
@@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1007</width>
|
<width>1002</width>
|
||||||
<height>819</height>
|
<height>838</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
<enum>Qt::LeftToRight</enum>
|
<enum>Qt::LeftToRight</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>6</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="tabsClosable">
|
<property name="tabsClosable">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
@@ -878,7 +878,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="cbTextToSpeach">
|
<widget class="QCheckBox" name="cbTextToSpeach">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Озвучить после !!!</string>
|
<string>Игнорировать все сообщения</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -974,6 +974,9 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="1" colspan="2">
|
||||||
|
<widget class="QComboBox" name="cbCounters"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
@@ -1084,7 +1087,521 @@
|
|||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>Навыки</string>
|
<string>Навыки</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_tab_skills"/>
|
<layout class="QVBoxLayout" name="verticalLayout_tab_skills">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_17">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_12">
|
||||||
|
<property name="title">
|
||||||
|
<string>Баллы канала</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_28">
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="sgCustomRewards"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_18">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_26">
|
||||||
|
<property name="text">
|
||||||
|
<string>Название:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="edtCRName"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_27">
|
||||||
|
<property name="text">
|
||||||
|
<string>Описание:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="edtCRPrompt"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_28">
|
||||||
|
<property name="text">
|
||||||
|
<string>Цена:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="sbCRCost">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>9999999</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>1000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_11">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_19">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnCRAdd">
|
||||||
|
<property name="text">
|
||||||
|
<string>Добавить</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnCREdit">
|
||||||
|
<property name="text">
|
||||||
|
<string>Изменить</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnCRDelete">
|
||||||
|
<property name="text">
|
||||||
|
<string>Удалить</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnCRGet">
|
||||||
|
<property name="text">
|
||||||
|
<string>Обновить</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_13">
|
||||||
|
<property name="title">
|
||||||
|
<string>Действия</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_30">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_29">
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="sgActions"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_20">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_29">
|
||||||
|
<property name="text">
|
||||||
|
<string>Действие:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="cbActions">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Нажать кнопки</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Воcпроизвести звук</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Показать уведомление</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="gbActionsKeys">
|
||||||
|
<property name="title">
|
||||||
|
<string>Нажатие кнопок</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_21">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_30">
|
||||||
|
<property name="text">
|
||||||
|
<string>Комбинация:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="edtKeyLine"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="gbActionsAudio">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Воспроизвести звук</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_23">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_31">
|
||||||
|
<property name="text">
|
||||||
|
<string>Файл:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="edtActionAudio"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnOpenAudioFile">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="gbActionsNotify">
|
||||||
|
<property name="title">
|
||||||
|
<string>Показать уведомление</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_32">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_24">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_32">
|
||||||
|
<property name="text">
|
||||||
|
<string>Загаловок:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit_3"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_25">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_33">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Plain</enum>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Описание:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit_4"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_26">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_34">
|
||||||
|
<property name="text">
|
||||||
|
<string>Картинка:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="edtActionPic"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnActionPicOpen">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_27">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_35">
|
||||||
|
<property name="text">
|
||||||
|
<string>Звук:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="edtActionSound"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnActionAudioOpen">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_22">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnAddAction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Добавить</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnDelAction">
|
||||||
|
<property name="text">
|
||||||
|
<string>Удалить</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_12">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_7">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_31">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<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>0</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::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_15">
|
||||||
|
<property name="title">
|
||||||
|
<string>Связи</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_34">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_31">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_38">
|
||||||
|
<property name="text">
|
||||||
|
<string>Событие:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="cbDonateList">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="lvLinks"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_32">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_39">
|
||||||
|
<property name="text">
|
||||||
|
<string>Действие:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="cbActionsList">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnLinksAdd">
|
||||||
|
<property name="text">
|
||||||
|
<string>Добавить</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnLinksDel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Удалить</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="tab_5">
|
<widget class="QWidget" name="tab_5">
|
||||||
<property name="icon" stdset="0">
|
<property name="icon" stdset="0">
|
||||||
@@ -1183,7 +1700,7 @@
|
|||||||
<item row="0" column="4">
|
<item row="0" column="4">
|
||||||
<widget class="QPushButton" name="btnNotifyOpen">
|
<widget class="QPushButton" name="btnNotifyOpen">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Открыть</string>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset>
|
||||||
@@ -1243,7 +1760,7 @@
|
|||||||
<item row="3" column="4">
|
<item row="3" column="4">
|
||||||
<widget class="QPushButton" name="btnNotifyOpenSub">
|
<widget class="QPushButton" name="btnNotifyOpenSub">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Открыть</string>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset>
|
||||||
@@ -1278,7 +1795,7 @@
|
|||||||
<item row="2" column="4">
|
<item row="2" column="4">
|
||||||
<widget class="QPushButton" name="btnNotifyOpenVip">
|
<widget class="QPushButton" name="btnNotifyOpenVip">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Открыть</string>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset>
|
||||||
@@ -1320,7 +1837,7 @@
|
|||||||
<item row="1" column="4">
|
<item row="1" column="4">
|
||||||
<widget class="QPushButton" name="btnNotifyOpenMod">
|
<widget class="QPushButton" name="btnNotifyOpenMod">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Открыть</string>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
<iconset>
|
<iconset>
|
||||||
@@ -1417,141 +1934,267 @@
|
|||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_tab_auto">
|
<layout class="QVBoxLayout" name="verticalLayout_tab_auto">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_10">
|
<layout class="QHBoxLayout" name="horizontalLayout_12">
|
||||||
<property name="title">
|
<property name="topMargin">
|
||||||
<string>Таймеры сообщений</string>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_groupBox_10">
|
<item>
|
||||||
<item>
|
<widget class="QGroupBox" name="groupBox_10">
|
||||||
<widget class="QTableWidget" name="sgTimers">
|
<property name="title">
|
||||||
<property name="rowCount">
|
<string>Таймеры сообщений</string>
|
||||||
<number>0</number>
|
</property>
|
||||||
</property>
|
<layout class="QVBoxLayout" name="verticalLayout_groupBox_10">
|
||||||
<property name="columnCount">
|
|
||||||
<number>4</number>
|
|
||||||
</property>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Вкл</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Сообщение</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Интервал (мин)</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>О</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_timer_edit">
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="lblTimerMessage">
|
<widget class="QTableWidget" name="sgTimers">
|
||||||
<property name="text">
|
<property name="rowCount">
|
||||||
<string>Сообщение:</string>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="columnCount">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Вкл</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Сообщение</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Интервал (мин)</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>О</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="edtTimerMessage"/>
|
<layout class="QHBoxLayout" name="horizontalLayout_timer_edit">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblTimerMessage">
|
||||||
|
<property name="text">
|
||||||
|
<string>Сообщение:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="edtTimerMessage"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblTimerInterval">
|
||||||
|
<property name="text">
|
||||||
|
<string>Интервал:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="edtTimerInterval">
|
||||||
|
<property name="text">
|
||||||
|
<string>10</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_timer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="lblTimerInterval">
|
<layout class="QHBoxLayout" name="horizontalLayout_timer_buttons">
|
||||||
<property name="text">
|
<item>
|
||||||
<string>Интервал:</string>
|
<widget class="QPushButton" name="btnTimerAdd">
|
||||||
</property>
|
<property name="text">
|
||||||
</widget>
|
<string>Добавить</string>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
<property name="icon">
|
||||||
<widget class="QLineEdit" name="edtTimerInterval">
|
<iconset>
|
||||||
<property name="text">
|
<normaloff>../../../Downloads/ico/add.png</normaloff>../../../Downloads/ico/add.png</iconset>
|
||||||
<string>10</string>
|
</property>
|
||||||
</property>
|
</widget>
|
||||||
</widget>
|
</item>
|
||||||
</item>
|
<item>
|
||||||
<item>
|
<widget class="QPushButton" name="btnTimerEdit">
|
||||||
<spacer name="horizontalSpacer_timer">
|
<property name="text">
|
||||||
<property name="orientation">
|
<string>Изменить</string>
|
||||||
<enum>Qt::Horizontal</enum>
|
</property>
|
||||||
</property>
|
<property name="icon">
|
||||||
<property name="sizeHint" stdset="0">
|
<iconset>
|
||||||
<size>
|
<normaloff>../../../Downloads/ico/edit.png</normaloff>../../../Downloads/ico/edit.png</iconset>
|
||||||
<width>40</width>
|
</property>
|
||||||
<height>20</height>
|
</widget>
|
||||||
</size>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</spacer>
|
<widget class="QPushButton" name="btnTimerDelete">
|
||||||
|
<property name="text">
|
||||||
|
<string>Удалить</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset>
|
||||||
|
<normaloff>../../../Downloads/ico/minus-sign_3485999.png</normaloff>../../../Downloads/ico/minus-sign_3485999.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnTimerTest">
|
||||||
|
<property name="text">
|
||||||
|
<string>Тест</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_timer_buttons">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</widget>
|
||||||
<item>
|
</item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_timer_buttons">
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_11">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTipDuration">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Счетчики</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_26">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="btnTimerAdd">
|
<widget class="QTableWidget" name="sgCounters">
|
||||||
<property name="text">
|
<column>
|
||||||
<string>Добавить</string>
|
<property name="text">
|
||||||
</property>
|
<string>Слово или фраза</string>
|
||||||
<property name="icon">
|
</property>
|
||||||
<iconset>
|
</column>
|
||||||
<normaloff>../../../Downloads/ico/add.png</normaloff>../../../Downloads/ico/add.png</iconset>
|
<column>
|
||||||
</property>
|
<property name="text">
|
||||||
|
<string>Количество</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="btnTimerEdit">
|
<layout class="QHBoxLayout" name="horizontalLayout_13">
|
||||||
<property name="text">
|
<property name="topMargin">
|
||||||
<string>Изменить</string>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<item>
|
||||||
<iconset>
|
<widget class="QLabel" name="label_24">
|
||||||
<normaloff>../../../Downloads/ico/edit.png</normaloff>../../../Downloads/ico/edit.png</iconset>
|
<property name="text">
|
||||||
</property>
|
<string>Слово:</string>
|
||||||
</widget>
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="edtWordCounter"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_25">
|
||||||
|
<property name="text">
|
||||||
|
<string>Количество:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="sbStartCounter"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_9">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="btnTimerDelete">
|
<layout class="QHBoxLayout" name="horizontalLayout_16">
|
||||||
<property name="text">
|
<property name="topMargin">
|
||||||
<string>Удалить</string>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<item>
|
||||||
<iconset>
|
<widget class="QPushButton" name="btnCounterAdd">
|
||||||
<normaloff>../../../Downloads/ico/minus-sign_3485999.png</normaloff>../../../Downloads/ico/minus-sign_3485999.png</iconset>
|
<property name="text">
|
||||||
</property>
|
<string>Добавить</string>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item>
|
</item>
|
||||||
<widget class="QPushButton" name="btnTimerTest">
|
<item>
|
||||||
<property name="text">
|
<widget class="QPushButton" name="btnCounterEdit">
|
||||||
<string>Тест</string>
|
<property name="text">
|
||||||
</property>
|
<string>Изменить</string>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item>
|
</item>
|
||||||
<spacer name="horizontalSpacer_timer_buttons">
|
<item>
|
||||||
<property name="orientation">
|
<widget class="QPushButton" name="btnCounterDelete">
|
||||||
<enum>Qt::Horizontal</enum>
|
<property name="text">
|
||||||
</property>
|
<string>Удалить</string>
|
||||||
<property name="sizeHint" stdset="0">
|
</property>
|
||||||
<size>
|
</widget>
|
||||||
<width>40</width>
|
</item>
|
||||||
<height>20</height>
|
<item>
|
||||||
</size>
|
<spacer name="horizontalSpacer_10">
|
||||||
</property>
|
<property name="orientation">
|
||||||
</spacer>
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</widget>
|
||||||
</layout>
|
</item>
|
||||||
</widget>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_auto">
|
<spacer name="verticalSpacer_auto">
|
||||||
|
|||||||
Reference in New Issue
Block a user