соединение с сервером событий
- получение информации о фоллов, подписках, рейдах, покупках наград за баллы
This commit is contained in:
@@ -43,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 \
|
||||||
@@ -81,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 \
|
||||||
|
|||||||
@@ -20,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
|
||||||
@@ -49,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
|
||||||
|
|||||||
@@ -20,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
|
||||||
@@ -49,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
|
||||||
|
|||||||
@@ -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);
|
connect(twitchAPI, &TTwAPI::rateLimitExceeded, this, &uGeneral::onRateLimit);
|
||||||
|
|
||||||
initTwitchAPI();
|
initTwitchAPI();
|
||||||
|
|
||||||
|
m_twitchEventSub = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void uGeneral::setupUserWidget()
|
void uGeneral::setupUserWidget()
|
||||||
@@ -832,6 +834,7 @@ uGeneral::~uGeneral()
|
|||||||
delete m_neuralTemplateManager;
|
delete m_neuralTemplateManager;
|
||||||
m_neuralTemplateManager = nullptr;
|
m_neuralTemplateManager = nullptr;
|
||||||
}
|
}
|
||||||
|
delete m_twitchEventSub;
|
||||||
delete m_counterManager;
|
delete m_counterManager;
|
||||||
delete m_createNotifyDialog;
|
delete m_createNotifyDialog;
|
||||||
delete m_createChatDialog;
|
delete m_createChatDialog;
|
||||||
@@ -1497,7 +1500,12 @@ void uGeneral::on_btnGetTokenStreamer_clicked()
|
|||||||
return;
|
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:subscriptions+"
|
||||||
"channel:read:redemptions+"
|
"channel:read:redemptions+"
|
||||||
"user:read:email";
|
"user:read:email";
|
||||||
@@ -2089,12 +2097,87 @@ void uGeneral::connectToTwitch()
|
|||||||
ui->lbBotDays->setText(QString::number(botTokenDays));
|
ui->lbBotDays->setText(QString::number(botTokenDays));
|
||||||
ui->lbStreamerDays->setText(QString::number(streamerTokenDays));
|
ui->lbStreamerDays->setText(QString::number(streamerTokenDays));
|
||||||
setTwitchConnected(true);
|
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()
|
void uGeneral::disconnectFromTwitch()
|
||||||
{
|
{
|
||||||
|
|
||||||
m_twitchClient->disconnectFromServer();
|
m_twitchClient->disconnectFromServer();
|
||||||
|
|
||||||
|
if (m_twitchEventSub) {
|
||||||
|
m_twitchEventSub->disconnectFromTwitch();
|
||||||
|
}
|
||||||
|
|
||||||
m_userManager->clear();
|
m_userManager->clear();
|
||||||
ui->pushButton_2->setText("Подключиться");
|
ui->pushButton_2->setText("Подключиться");
|
||||||
setTwitchConnected(false);
|
setTwitchConnected(false);
|
||||||
|
|||||||
+2
-1
@@ -17,6 +17,7 @@
|
|||||||
#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"
|
||||||
@@ -420,7 +421,7 @@ private:
|
|||||||
MediaFileManager *m_TextFiles;
|
MediaFileManager *m_TextFiles;
|
||||||
NeuralTemplateManager *m_neuralTemplateManager;
|
NeuralTemplateManager *m_neuralTemplateManager;
|
||||||
DonationManager *m_donationManager;
|
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
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@
|
|||||||
<enum>Qt::LeftToRight</enum>
|
<enum>Qt::LeftToRight</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>3</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="tabsClosable">
|
<property name="tabsClosable">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
|
|||||||
Reference in New Issue
Block a user