594 lines
17 KiB
C++
594 lines
17 KiB
C++
#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);
|
||
}
|
||
}
|