#include "ttw_api.h" #include "qeventloop.h" #include #include #include #include #include #include #include #include TTwAPI::TTwAPI(QObject *parent) : QObject(parent) , m_networkManager(new QNetworkAccessManager(this)) { // Настройка таймаутов //m_networkManager->Timeout(30000); // 30 секунд } TTwAPI::~TTwAPI() { m_networkManager->deleteLater(); } void TTwAPI::init(const QString &clientId, const QString &token, const QString &streamerToken, const QString &channel, const QString &botName) { m_clientId = clientId; m_tokenApi = token; m_tokenApiStreamer = streamerToken; m_channelName = channel; m_botName = botName; toLog(1, "TTwAPI::init", "API инициализирован для канала: " + channel); } QString TTwAPI::sendRequest(const QString &url, const QString &method, const QByteArray &data, const QString &token) { QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setRawHeader("Client-ID", m_clientId.toUtf8()); QString authHeader = token.startsWith("Bearer") ? token : "Bearer " + token; request.setRawHeader("Authorization", authHeader.toUtf8()); QNetworkReply *reply = nullptr; if (method.toUpper() == "GET") { reply = m_networkManager->get(request); } else if (method.toUpper() == "POST") { reply = m_networkManager->post(request, data); } else if (method.toUpper() == "DELETE") { reply = m_networkManager->sendCustomRequest(request, "DELETE", data); } else if (method.toUpper() == "PATCH") { reply = m_networkManager->sendCustomRequest(request, "PATCH", data); } else if (method.toUpper() == "PUT") { reply = m_networkManager->sendCustomRequest(request, "PUT", data); } if (!reply) { toLog(2, "TTwAPI::sendRequest", "Не удалось создать запрос: " + url); return QString(); } // Ожидание завершения запроса (синхронно) QEventLoop loop; connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); // Проверка ошибок if (reply->error() != QNetworkReply::NoError) { QString errorMsg = QString("Ошибка %1: %2").arg(reply->error()).arg(reply->errorString()); toLog(2, "TTwAPI::sendRequest", errorMsg); if (reply->error() == QNetworkReply::AuthenticationRequiredError) { emit tokenExpired(errorMsg); } reply->deleteLater(); return QString(); } QString response = reply->readAll(); reply->deleteLater(); return response; } QString TTwAPI::getTTW(const QString &method, const QString &clientId, bool isStreamer) { Q_UNUSED(clientId); QString baseUrl = "https://api.twitch.tv/helix/"; QString token = isStreamer ? m_tokenApiStreamer : m_tokenApi; return sendRequest(baseUrl + method, "GET", QByteArray(), token); } QString TTwAPI::deleteTTW(const QString &method, const QString &clientId, bool isStreamer) { Q_UNUSED(clientId); QString baseUrl = "https://api.twitch.tv/helix/"; QString token = isStreamer ? m_tokenApiStreamer : m_tokenApi; return sendRequest(baseUrl + method, "DELETE", QByteArray(), token); } QString TTwAPI::postTTW(const QString &method, const QString &clientId, const QByteArray &data, bool isStreamer) { Q_UNUSED(clientId); QString baseUrl = "https://api.twitch.tv/helix/"; QString token = isStreamer ? m_tokenApiStreamer : m_tokenApi; return sendRequest(baseUrl + method, "POST", data, token); } QString TTwAPI::patchTTW(const QString &method, const QString &clientId, const QByteArray &data, bool isStreamer) { Q_UNUSED(clientId); QString baseUrl = "https://api.twitch.tv/helix/"; QString token = isStreamer ? m_tokenApiStreamer : m_tokenApi; return sendRequest(baseUrl + method, "PATCH", data, token); } void TTwAPI::getTTWStat(const QString &channel, int &avgViewers, int &maxViewers, int &hoursWatched, int &followers, int &followersTotal) { Q_UNUSED(channel); QString response = getTTW("channels?broadcaster_id=" + getRoomId(), m_clientId); if (response.isEmpty()) { toLog(2, "TTwAPI::getTTWStat", "Пустой ответ от API"); return; } QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); if (!doc.isObject()) { toLog(2, "TTwAPI::getTTWStat", "Неверный JSON формат"); return; } QJsonObject root = doc.object(); QJsonArray data = root["data"].toArray(); if (data.isEmpty()) { toLog(2, "TTwAPI::getTTWStat", "Нет данных в ответе"); return; } QJsonObject channelData = data[0].toObject(); // Парсинг данных (заглушка - реальные поля могут отличаться) avgViewers = channelData["average_viewers"].toInt(); maxViewers = channelData["peak_viewers"].toInt(); hoursWatched = channelData["hours_watched"].toInt(); followers = channelData["followers_gained"].toInt(); followersTotal = channelData["followers_total"].toInt(); } QDate TTwAPI::getFollow(const QString &id) { // Правильный endpoint для проверки, является ли пользователь фоловером QString response = getTTW( QString("channels/followers?user_id=%1&broadcaster_id=%2") .arg(id) .arg(getRoomId()), m_clientId, true ); if (response.isEmpty()) { return QDate(); } QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); if (!doc.isObject()) { return QDate(); } QJsonObject root = doc.object(); QJsonArray data = root["data"].toArray(); if (data.isEmpty()) { // Пользователь не фоловер return QDate(); } QJsonObject followData = data[0].toObject(); QString followedAt = followData["followed_at"].toString(); return QDate::fromString(followedAt, Qt::ISODate); } User TTwAPI::getUserByLogin(const QString &login) { User user; user.login = login.toLower(); QString response = getTTW("users?login=" + login, m_clientId); if (response.isEmpty()) { toLog(2, "TTwAPI::getUserByLogin", "Не удалось получить данные пользователя: " + login); return user; } QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); if (!doc.isObject()) { return user; } QJsonObject root = doc.object(); QJsonArray data = root["data"].toArray(); if (data.isEmpty()) { return user; } QJsonObject userData = data[0].toObject(); user.id = userData["id"].toString(); user.displayName = userData["display_name"].toString(); user.createdAt = QDate::fromString(userData["created_at"].toString(), Qt::ISODate); // Получаем информацию о подписке user.followAt = getFollow(user.id); return user; } void TTwAPI::setModerator(const QString &id) { QJsonDocument doc((QJsonObject())); postTTW("moderation/moderators?broadcaster_id=" + getRoomId() + "&user_id=" + id, m_clientId, doc.toJson(), true); } void TTwAPI::delModerator(const QString &id) { deleteTTW("moderation/moderators?broadcaster_id=" + getRoomId() + "&user_id=" + id, m_clientId, true); } void TTwAPI::setVIP(const QString &id) { QJsonDocument doc((QJsonObject())); postTTW("channels/vips?broadcaster_id=" + getRoomId() + "&user_id=" + id, m_clientId, doc.toJson(), true); } void TTwAPI::delVIP(const QString &id) { deleteTTW("channels/vips?broadcaster_id=" + getRoomId() + "&user_id=" + id, m_clientId, true); } void TTwAPI::banUser(const QString &id) { QString roomId = getRoomId(); if (roomId.isEmpty()) { toLog(2, "TTwAPI::banUser", "Не удалось получить roomId"); return; } QString botId = getBotId(); // Нужно получить ID бота if (botId.isEmpty()) { toLog(2, "TTwAPI::banUser", "Не удалось получить botId"); return; } QJsonObject innerData; innerData["user_id"] = id; innerData["reason"] = "Нарушение правил чата"; QJsonObject dataObj; dataObj["data"] = innerData; QJsonDocument doc(dataObj); postTTW("moderation/bans?broadcaster_id=" + roomId + "&moderator_id=" + botId, m_clientId, doc.toJson(), false); } void TTwAPI::banUserTime(const QString &id, int timeMinutes) { QString roomId = getRoomId(); if (roomId.isEmpty()) { toLog(2, "TTwAPI::banUserTime", "Не удалось получить roomId"); return; } QString botId = getBotId(); if (botId.isEmpty()) { toLog(2, "TTwAPI::banUserTime", "Не удалось получить botId"); return; } QJsonObject innerData; innerData["user_id"] = id; innerData["duration"] = timeMinutes * 60; innerData["reason"] = "Таймаут"; QJsonObject dataObj; dataObj["data"] = innerData; QJsonDocument doc(dataObj); postTTW("moderation/bans?broadcaster_id=" + roomId + "&moderator_id=" + botId, m_clientId, doc.toJson(), false); } void TTwAPI::warnUser(const QString &id) { QString roomId = getRoomId(); if (roomId.isEmpty()) { toLog(2, "TTwAPI::warnUser", "Не удалось получить roomId"); return; } QString botId = getBotId(); if (botId.isEmpty()) { toLog(2, "TTwAPI::warnUser", "Не удалось получить botId"); return; } QJsonObject innerData; innerData["user_id"] = id; QJsonObject dataObj; dataObj["data"] = innerData; QJsonDocument doc(dataObj); postTTW("moderation/warnings?broadcaster_id=" + roomId + "&moderator_id=" + botId, m_clientId, doc.toJson(), false); } void TTwAPI::unbanUser(const QString &id) { QString roomId = getRoomId(); if (roomId.isEmpty()) { toLog(2, "TTwAPI::unbanUser", "Не удалось получить roomId"); return; } QString botId = getBotId(); if (botId.isEmpty()) { toLog(2, "TTwAPI::unbanUser", "Не удалось получить botId"); return; } deleteTTW("moderation/bans?broadcaster_id=" + roomId + "&moderator_id=" + botId + "&user_id=" + id, m_clientId, false); } QString TTwAPI::getBotId() { static QString cachedBotId; if (!cachedBotId.isEmpty()) { return cachedBotId; } if (m_botName.isEmpty()) { toLog(2, "TTwAPI::getBotId", "Имя бота не установлено"); return QString(); } QString response = getTTW("users?login=" + m_botName, m_clientId); if (response.isEmpty()) { toLog(2, "TTwAPI::getBotId", "Не удалось получить данные бота"); return QString(); } QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); if (!doc.isObject()) { toLog(2, "TTwAPI::getBotId", "Неверный JSON формат"); return QString(); } QJsonObject root = doc.object(); QJsonArray data = root["data"].toArray(); if (data.isEmpty()) { toLog(2, "TTwAPI::getBotId", "Нет данных о боте"); return QString(); } cachedBotId = data[0].toObject()["id"].toString(); return cachedBotId; } QString TTwAPI::getFollowedAtFromJson(const QString &jsonString) { QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8()); if (!doc.isObject()) { return QString(); } QJsonObject root = doc.object(); QJsonArray data = root["data"].toArray(); if (data.isEmpty()) { return QString(); } QJsonObject followData = data[0].toObject(); return followData["followed_at"].toString(); } QString TTwAPI::getRoomId() { // Кэширование ID комнаты static QString cachedRoomId; if (!cachedRoomId.isEmpty()) { return cachedRoomId; } QString response = getTTW("users?login=" + m_channelName, m_clientId); if (response.isEmpty()) { return QString(); } QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); if (!doc.isObject()) { return QString(); } QJsonObject root = doc.object(); QJsonArray data = root["data"].toArray(); if (data.isEmpty()) { return QString(); } cachedRoomId = data[0].toObject()["id"].toString(); return cachedRoomId; } QString TTwAPI::getRoomAndBot() { QString roomId = getRoomId(); QString botId; QString response = getTTW("users?login=" + m_botName, m_clientId); if (!response.isEmpty()) { QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8()); QJsonObject root = doc.object(); QJsonArray data = root["data"].toArray(); if (!data.isEmpty()) { botId = data[0].toObject()["id"].toString(); } } return QString("RoomID: %1, BotID: %2").arg(roomId).arg(botId); } void TTwAPI::toLog(int level, const QString &method, const QString &message) { Q_UNUSED(level); Q_UNUSED(method); Q_UNUSED(message); // uGeneral.toLog("ttw_api", method, message, level); } void TTwAPI::sendAnnouncement(const QString &message) { QString roomId = getRoomId(); if (roomId.isEmpty()) { toLog(2, "TTwAPI::sendAnnouncement", "Не удалось получить roomId"); return; } QString botId = getBotId(); if (botId.isEmpty()) { toLog(2, "TTwAPI::sendAnnouncement", "Не удалось получить botId"); return; } QJsonObject json; json["message"] = message; json["color"] = "primary"; // Можно сделать настраиваемым: "primary", "blue", "green", "orange", "purple" QJsonDocument doc(json); QString otv = postTTW("chat/announcements?broadcaster_id=" + roomId + "&moderator_id=" + botId, m_clientId, doc.toJson(), false); } void TTwAPI::sendAnnouncementToChat(const QString &message) { // Этот метод может использоваться для обычных сообщений в чат // Но для Twitch API это тот же endpoint sendAnnouncement(message); } void TTwAPI::getGlobalChatBadges(QVector &badges) { QString response = getTTW("chat/badges/global", m_clientId, false); parseBadgesFromApi(response, badges); } void TTwAPI::getCustomChatBadges(QVector &badges) { QString roomId = getRoomId(); if (roomId.isEmpty()) { toLog(2, "TTwAPI::getCustomChatBadges", "Не удалось получить roomId"); return; } QString response = getTTW("chat/badges?broadcaster_id=" + roomId, m_clientId, false); parseBadgesFromApi(response, badges); } void TTwAPI::parseBadgesFromApi(const QString &jsonString, QVector &badges) { if (jsonString.isEmpty()) { return; } QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8()); if (!doc.isObject()) { toLog(2, "TTwAPI::parseBadgesFromApi", "Неверный JSON формат"); return; } QJsonObject root = doc.object(); QJsonArray data = root["data"].toArray(); for (const QJsonValue &value : data) { QJsonObject badgeObj = value.toObject(); ChatBadge badge; badge.setId = badgeObj["set_id"].toString(); QJsonArray versions = badgeObj["versions"].toArray(); for (const QJsonValue &versionValue : versions) { QJsonObject versionObj = versionValue.toObject(); BadgeVersion version; version.id = versionObj["id"].toString(); version.imageUrl1x = versionObj["image_url_1x"].toString(); version.imageUrl2x = versionObj["image_url_2x"].toString(); version.imageUrl4x = versionObj["image_url_4x"].toString(); version.title = versionObj["title"].toString(); version.description = versionObj["description"].toString(); badge.versions.append(version); } badges.append(badge); } }