соединение с сервером событий
- получение информации о фоллов, подписках, рейдах, покупках наград за баллы
This commit is contained in:
@@ -43,6 +43,7 @@ SOURCES += \
|
||||
soundmanager.cpp \
|
||||
tauth.cpp \
|
||||
ttw_api.cpp \
|
||||
twitcheventsub.cpp \
|
||||
twitchmessage.cpp \
|
||||
udatabase.cpp \
|
||||
ugeneral.cpp \
|
||||
@@ -81,6 +82,7 @@ HEADERS += \
|
||||
timerinfo.h \
|
||||
ttw_api.h \
|
||||
ttw_types.h \
|
||||
twitcheventsub.h \
|
||||
twitchmessage.h \
|
||||
udatabase.h \
|
||||
ugeneral.h \
|
||||
|
||||
@@ -20,6 +20,7 @@ debug/randomresponses.o
|
||||
debug/soundmanager.o
|
||||
debug/tauth.o
|
||||
debug/ttw_api.o
|
||||
debug/twitcheventsub.o
|
||||
debug/twitchmessage.o
|
||||
debug/udatabase.o
|
||||
debug/ugeneral.o
|
||||
@@ -49,6 +50,7 @@ debug/moc_randomresponses.o
|
||||
debug/moc_soundmanager.o
|
||||
debug/moc_tauth.o
|
||||
debug/moc_ttw_api.o
|
||||
debug/moc_twitcheventsub.o
|
||||
debug/moc_udatabase.o
|
||||
debug/moc_ugeneral.o
|
||||
debug/moc_ulink.o
|
||||
|
||||
@@ -20,6 +20,7 @@ release/randomresponses.o
|
||||
release/soundmanager.o
|
||||
release/tauth.o
|
||||
release/ttw_api.o
|
||||
release/twitcheventsub.o
|
||||
release/twitchmessage.o
|
||||
release/udatabase.o
|
||||
release/ugeneral.o
|
||||
@@ -49,6 +50,7 @@ release/moc_randomresponses.o
|
||||
release/moc_soundmanager.o
|
||||
release/moc_tauth.o
|
||||
release/moc_ttw_api.o
|
||||
release/moc_twitcheventsub.o
|
||||
release/moc_udatabase.o
|
||||
release/moc_ugeneral.o
|
||||
release/moc_ulink.o
|
||||
|
||||
@@ -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
|
||||
+84
-1
@@ -675,6 +675,8 @@ void uGeneral::setupTwitchComponents()
|
||||
connect(twitchAPI, &TTwAPI::rateLimitExceeded, this, &uGeneral::onRateLimit);
|
||||
|
||||
initTwitchAPI();
|
||||
|
||||
m_twitchEventSub = nullptr;
|
||||
}
|
||||
|
||||
void uGeneral::setupUserWidget()
|
||||
@@ -832,6 +834,7 @@ uGeneral::~uGeneral()
|
||||
delete m_neuralTemplateManager;
|
||||
m_neuralTemplateManager = nullptr;
|
||||
}
|
||||
delete m_twitchEventSub;
|
||||
delete m_counterManager;
|
||||
delete m_createNotifyDialog;
|
||||
delete m_createChatDialog;
|
||||
@@ -1497,7 +1500,12 @@ void uGeneral::on_btnGetTokenStreamer_clicked()
|
||||
return;
|
||||
}
|
||||
|
||||
QString scope = "channel:manage:broadcast+"
|
||||
QString scope =
|
||||
"channel:manage:vips+"
|
||||
"moderator:read:followers+"
|
||||
"channel:manage:moderators+"
|
||||
"channel:manage:redemptions+"
|
||||
"channel:manage:broadcast+"
|
||||
"channel:read:subscriptions+"
|
||||
"channel:read:redemptions+"
|
||||
"user:read:email";
|
||||
@@ -2089,12 +2097,87 @@ void uGeneral::connectToTwitch()
|
||||
ui->lbBotDays->setText(QString::number(botTokenDays));
|
||||
ui->lbStreamerDays->setText(QString::number(streamerTokenDays));
|
||||
setTwitchConnected(true);
|
||||
|
||||
// === Инициализация EventSub ===
|
||||
if (!m_twitchEventSub) {
|
||||
m_twitchEventSub = new TwitchEventSub(this);
|
||||
|
||||
// Подключаем сигналы (например, для логирования)
|
||||
connect(m_twitchEventSub, &TwitchEventSub::onLog,
|
||||
[this](int level, const QString &method, const QString &msg) {
|
||||
LogManager::instance()->log(static_cast<LogLevel>(level), "TwitchEventSub", method, msg);
|
||||
});
|
||||
|
||||
connect(m_twitchEventSub, &TwitchEventSub::onError,
|
||||
[this](const QString &error) {
|
||||
LogManager::instance()->error("TwitchEventSub", "onError", error);
|
||||
});
|
||||
|
||||
// Здесь можно подключить обработчики конкретных событий
|
||||
connect(m_twitchEventSub, &TwitchEventSub::onCustomReward,
|
||||
[this](const TCustomRewardEvent2 &data) {
|
||||
LogManager::instance()->info("TwitchEventSub", "onCustomReward",
|
||||
QString("Reward: %1 by %2")
|
||||
.arg(data.event.reward.title)
|
||||
.arg(data.event.user_name));
|
||||
// TODO: вызвать нужный метод (например, уведомление, звук)
|
||||
});
|
||||
|
||||
connect(m_twitchEventSub, &TwitchEventSub::onFollow,
|
||||
[this](const TFollowEvent &data) {
|
||||
LogManager::instance()->info("TwitchEventSub", "onFollow",
|
||||
QString("%1 followed").arg(data.event.user_name));
|
||||
// playNotify(...); или другое
|
||||
});
|
||||
|
||||
connect(m_twitchEventSub, &TwitchEventSub::onSubscribe,
|
||||
[this](const TSubEvent &data) {
|
||||
LogManager::instance()->info("TwitchEventSub", "onSubscribe",
|
||||
QString("%1 subscribed (tier %2)")
|
||||
.arg(data.event.user_name)
|
||||
.arg(data.event.tier));
|
||||
});
|
||||
|
||||
connect(m_twitchEventSub, &TwitchEventSub::onGift,
|
||||
[this](const TGiftEvent &data) {
|
||||
LogManager::instance()->info("TwitchEventSub", "onGift",
|
||||
QString("%1 gifted %2 subs")
|
||||
.arg(data.event.user_name)
|
||||
.arg(data.event.total));
|
||||
});
|
||||
|
||||
connect(m_twitchEventSub, &TwitchEventSub::onRaid,
|
||||
[this](const TRaidEvent &data) {
|
||||
LogManager::instance()->info("TwitchEventSub", "onRaid",
|
||||
QString("Raid from %1 with %2 viewers")
|
||||
.arg(data.event.from_broadcaster_user_name)
|
||||
.arg(data.event.viewers));
|
||||
});
|
||||
}
|
||||
|
||||
// Получаем broadcaster_id через Twitch API (предположим, есть метод)
|
||||
QString broadcasterId = twitchAPI->getUserByLogin(ui->edtChannel->text()).id;
|
||||
if (broadcasterId.isEmpty()) {
|
||||
LogManager::instance()->warning("uGeneral", "connectToTwitch",
|
||||
"Could not resolve broadcaster ID, EventSub may fail");
|
||||
}
|
||||
|
||||
m_twitchEventSub->init(ui->edtBotTokenStreamer->text(), ui->edtBotClientID->text(), broadcasterId);
|
||||
m_twitchEventSub->connectToTwitch();
|
||||
|
||||
setTwitchConnected(true);
|
||||
}
|
||||
}
|
||||
|
||||
void uGeneral::disconnectFromTwitch()
|
||||
{
|
||||
|
||||
m_twitchClient->disconnectFromServer();
|
||||
|
||||
if (m_twitchEventSub) {
|
||||
m_twitchEventSub->disconnectFromTwitch();
|
||||
}
|
||||
|
||||
m_userManager->clear();
|
||||
ui->pushButton_2->setText("Подключиться");
|
||||
setTwitchConnected(false);
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@
|
||||
#include "logmanager.h"
|
||||
#include "neuralnetworkmanager.h"
|
||||
#include "ttw_api.h"
|
||||
#include "twitcheventsub.h"
|
||||
#include "user_manager.h"
|
||||
#include "webserverchat.h"
|
||||
#include "webservernotify.h"
|
||||
@@ -420,7 +421,7 @@ private:
|
||||
MediaFileManager *m_TextFiles;
|
||||
NeuralTemplateManager *m_neuralTemplateManager;
|
||||
DonationManager *m_donationManager;
|
||||
|
||||
TwitchEventSub *m_twitchEventSub = nullptr;
|
||||
QList<TimerInfo> m_timers; // Список таймеров
|
||||
int m_nextTimerId = 1; // Следующий ID таймера
|
||||
bool m_isTwitchConnected = false; // Статус подключения к Twitch
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>3</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="tabsClosable">
|
||||
<bool>false</bool>
|
||||
|
||||
Reference in New Issue
Block a user