first commit

This commit is contained in:
2026-01-26 22:26:19 +03:00
commit 31fccd85f2
95 changed files with 115400 additions and 0 deletions
+593
View File
@@ -0,0 +1,593 @@
#include "ttw_api.h"
#include "qeventloop.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QDateTime>
#include <QDebug>
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<ChatBadge> &badges)
{
QString response = getTTW("chat/badges/global", m_clientId, false);
parseBadgesFromApi(response, badges);
}
void TTwAPI::getCustomChatBadges(QVector<ChatBadge> &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<ChatBadge> &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);
}
}