TTW_Bot/twitcheventsub.cpp

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;
}