TTW_Bot/websocketclient.cpp

272 lines
7.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "websocketclient.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
#include <QCoreApplication>
#include <synchapi.h>
#include "logmanager.h"
#include "qregularexpression.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;
}
bool 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";
return 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)
{
QStringList lines = message.split("\r\n", Qt::SkipEmptyParts);
for (const QString &line : lines) {
if (line.isEmpty()) continue;
qDebug() << "RAW LINE:" << line;
// Определяем тип сообщения
bool isPing = line.startsWith("PING");
bool isPrivmsg = false;
bool isUserState = line.contains("USERSTATE");
bool isRoomState = line.contains("ROOMSTATE");
bool isUserNotice = line.contains("USERNOTICE");
bool isJoin = line.contains(" JOIN ");
bool isNamesList = (line.contains("353") || line.contains("366"));
bool isCapAck = line.contains("CAP * ACK");
// Проверяем PRIVMSG отдельно, чтобы избежать ложных срабатываний
if (line.contains("PRIVMSG", Qt::CaseInsensitive)) {
// Проверяем структуру PRIVMSG сообщения
// Должно быть: @теги :префикс PRIVMSG #канал :сообщение
if (!isUserState && !isRoomState && !isUserNotice) {
// Проверяем наличие канала и текста сообщения
if (line.contains(" #") && line.contains(" :")) {
isPrivmsg = true;
}
}
}
// Обработка в зависимости от типа
if (isPing) {
send("PONG :tmi.twitch.tv\r\n");
}
else if (isCapAck) {
qDebug() << "Capabilities acknowledged";
}
else if (isPrivmsg) {
qDebug() << "PRIVMSG detected (final):" << line;
emit onNewMessage(line);
}
else if (isJoin) {
qDebug() << "JOIN detected:" << line;
// emit onUserJoined(line);
}
else if (isUserState) {
qDebug() << "USERSTATE detected:" << line;
// emit onUserState(line);
}
else if (isRoomState) {
qDebug() << "ROOMSTATE detected:" << line;
// emit onRoomState(line);
}
else if (isUserNotice) {
qDebug() << "USERNOTICE detected:" << line;
// emit onUserNotice(line);
}
else if (isNamesList) {
qDebug() << "NAMES list:" << line;
}
else if (line.contains("001") && !m_currentChannel.isEmpty()) {
joinChannel(m_currentChannel);
}
else {
qDebug() << "OTHER message:" << line;
}
}
}
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;
}