Files
TTW_Bot/websocketclient.cpp
T
2026-01-26 22:26:19 +03:00

239 lines
6.7 KiB
C++

#include "websocketclient.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
#include <QCoreApplication>
#include <synchapi.h>
WebSocketClient::WebSocketClient(QObject *parent) :
QObject(parent),
m_webSocket(nullptr),
m_isConnected(false)
{
m_pingTimer = new QTimer(this);
m_pingTimer->setInterval(60000); // Ping каждые 30 секунд
m_channelJoined = false;
// Используем новый синтаксис подключения сигналов для Qt5
connect(m_pingTimer, &QTimer::timeout, this, &WebSocketClient::onPingTimeout);
}
WebSocketClient::~WebSocketClient()
{
disconnectFromServer();
delete m_webSocket;
}
bool WebSocketClient::connectToServer(const QString &url)
{
if (m_webSocket) {
disconnectFromServer();
delete m_webSocket;
m_webSocket = nullptr;
}
m_webSocket = new QWebSocket();
// Подключаем сигналы с новым синтаксисом Qt5
connect(m_webSocket, &QWebSocket::connected,
this, &WebSocketClient::onConnectedInternal);
connect(m_webSocket, &QWebSocket::disconnected,
this, &WebSocketClient::onDisconnectedInternal);
connect(m_webSocket, &QWebSocket::textMessageReceived,
this, &WebSocketClient::onTextMessageReceived);
connect(m_webSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
this, &WebSocketClient::onErrorInternal);
connect(m_webSocket, &QWebSocket::sslErrors,
this, &WebSocketClient::onSslErrors);
m_webSocket->open(QUrl(url));
return true;
}
void WebSocketClient::connectToTwitchChat(const QString &oauthToken,
const QString &nickname,
const QString &channel)
{
m_nickname = nickname;
m_currentChannel = channel;
m_oauthToken = oauthToken;
// URL для подключения к Twitch IRC через WebSocket
QString url = "wss://irc-ws.chat.twitch.tv:443";
connectToServer(url);
}
void WebSocketClient::onConnectedInternal()
{
m_isConnected = true;
m_pingTimer->start();
// Аутентификация в Twitch
authenticate(m_oauthToken, m_nickname);
emit onConnected();
}
void WebSocketClient::onDisconnectedInternal()
{
m_isConnected = false;
m_pingTimer->stop();
m_channelJoined = false;
emit onDisconnected();
}
void WebSocketClient::send(const QString &data)
{
if (m_webSocket && m_isConnected) {
m_webSocket->sendTextMessage(data);
} else {
}
}
void WebSocketClient::sendMessage(const QString &message)
{
send(message);
}
void WebSocketClient::sendIrcCommand(const QString &command, const QString &parameters)
{
QString message = command;
if (!parameters.isEmpty()) {
message += " " + parameters;
}
message += "\r\n";
send(message);
}
void WebSocketClient::authenticate(const QString &oauthToken, const QString &nickname)
{
// PASS команда для аутентификации
if (!oauthToken.startsWith("oauth:")) {
sendIrcCommand("PASS", "oauth:" + oauthToken);
} else {
sendIrcCommand("PASS", oauthToken);
}
Sleep(1000);
// NICK команда
sendIrcCommand("NICK", nickname);
// CAP REQ для получения дополнительных возможностей
sendIrcCommand("CAP REQ", ":twitch.tv/membership twitch.tv/tags twitch.tv/commands");
}
void WebSocketClient::joinChannel(const QString &channel)
{
if (m_isConnected) {
m_channelJoined = true;
sendIrcCommand("JOIN", "#" + channel.toLower());
}
}
void WebSocketClient::sendChatMessage(const QString &channel, const QString &message)
{
if (m_isConnected) {
sendIrcCommand("PRIVMSG", "#" + channel.toLower() + " :" + message);
}
}
void WebSocketClient::pingServer()
{
if (m_isConnected) {
send("PING :tmi.twitch.tv\r\n");
}
}
void WebSocketClient::onPingTimeout()
{
pingServer();
}
void WebSocketClient::onTextMessageReceived(const QString &message)
{
// Обработка PING от сервера
if (message.startsWith("PING")) {
send("PONG :tmi.twitch.tv\r\n");
return;
}
// Обработка успешной аутентификации
if (message.contains("001") && !m_currentChannel.isEmpty()) {
joinChannel(m_currentChannel);
return; // Добавить return после первого успешного подключения
}
// Обработка сообщений чата
if (message.contains("PRIVMSG")) {
// Извлекаем информацию из сообщения
// Формат: :nick!nick@nick.tmi.twitch.tv PRIVMSG #channel :message
QString cleanMessage = message;
emit onNewMessage(cleanMessage);
// Удаляем служебную информацию для чистого отображения
int msgIndex = cleanMessage.indexOf("PRIVMSG");
if (msgIndex != -1) {
int channelIndex = cleanMessage.indexOf("#", msgIndex);
int messageIndex = cleanMessage.indexOf(" :", channelIndex);
if (messageIndex != -1) {
QString chatMessage = cleanMessage.mid(messageIndex + 2).trimmed();
QString sender = cleanMessage.mid(1, cleanMessage.indexOf("!") - 1);
QString channel = cleanMessage.mid(channelIndex + 1,
messageIndex - channelIndex - 1);
}
}
}
}
void WebSocketClient::onErrorInternal(QAbstractSocket::SocketError error)
{
QString errorMsg = QString("WebSocket error: %1 - %2")
.arg(error)
.arg(m_webSocket ? m_webSocket->errorString() : "Unknown error");
emit onError(errorMsg);
}
void WebSocketClient::onSslErrors(const QList<QSslError> &errors)
{
QStringList errorMessages;
foreach (const QSslError &error, errors) {
errorMessages.append(error.errorString());
}
// В разработке можно игнорировать SSL ошибки, но в продакшене это опасно
if (m_webSocket) {
m_webSocket->ignoreSslErrors();
}
}
void WebSocketClient::disconnectFromServer()
{
if (m_webSocket && m_isConnected) {
// Отправляем команду выхода из канала для Twitch
if (!m_currentChannel.isEmpty()) {
sendIrcCommand("PART", "#" + m_currentChannel.toLower());
}
m_webSocket->close();
m_isConnected = false;
m_pingTimer->stop();
}
}
bool WebSocketClient::isConnected() const
{
return m_isConnected;
}