353 lines
15 KiB
C++
353 lines
15 KiB
C++
#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;
|
|
}
|
|
|