419 lines
13 KiB
C++
419 lines
13 KiB
C++
// emoteprovider.cpp
|
||
#include "emoteprovider.h"
|
||
#include <QJsonDocument>
|
||
#include <QJsonArray>
|
||
#include <QJsonObject>
|
||
#include <QJsonValue>
|
||
|
||
// EmoteProvider implementation
|
||
EmoteProvider::EmoteProvider(QObject *parent)
|
||
: QObject(parent)
|
||
, m_networkManager(new QNetworkAccessManager(this)) {
|
||
}
|
||
|
||
EmoteProvider::~EmoteProvider() {
|
||
// QNetworkAccessManager будет удален автоматически как дочерний объект
|
||
}
|
||
|
||
void EmoteProvider::setLogCallback(LogCallback callback) {
|
||
m_logCallback = callback;
|
||
}
|
||
|
||
void EmoteProvider::log(const QString &method, const QString &message, LogLevelemt level) {
|
||
if (m_logCallback) {
|
||
m_logCallback(metaObject()->className(), method, message, level);
|
||
}
|
||
}
|
||
|
||
// BTTVProvider implementation
|
||
BTTVProvider::BTTVProvider(QObject *parent)
|
||
: EmoteProvider(parent) {
|
||
}
|
||
|
||
BTTVProvider::~BTTVProvider() {
|
||
m_emotes.clear();
|
||
}
|
||
|
||
void BTTVProvider::fetchGlobal() {
|
||
log("fetchGlobal", "Fetching global BTTV emotes", LOG_INFO);
|
||
|
||
QNetworkRequest request(QUrl(getBaseUrl() + "emotes/global"));
|
||
request.setHeader(QNetworkRequest::UserAgentHeader,
|
||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36");
|
||
|
||
QNetworkReply *reply = m_networkManager->get(request);
|
||
connect(reply, &QNetworkReply::finished,
|
||
this, [this, reply]() { onGlobalReplyFinished(reply); });
|
||
}
|
||
|
||
void BTTVProvider::fetchCustom(const QString &userId) {
|
||
if (userId.isEmpty()) {
|
||
log("fetchCustom", "User ID is empty", LOG_WARNING);
|
||
return;
|
||
}
|
||
|
||
log("fetchCustom", QString("Fetching custom BTTV emotes for user: %1").arg(userId), LOG_INFO);
|
||
m_lastUserId = userId;
|
||
|
||
QNetworkRequest request(QUrl(getBaseUrl() + "users/twitch/" + userId));
|
||
request.setHeader(QNetworkRequest::UserAgentHeader,
|
||
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36");
|
||
|
||
QNetworkReply *reply = m_networkManager->get(request);
|
||
connect(reply, &QNetworkReply::finished,
|
||
this, [this, reply]() { onCustomReplyFinished(reply); });
|
||
}
|
||
|
||
QString BTTVProvider::getEmoteUrl(const QString &emoteName) {
|
||
for (const auto &emote : m_emotes) {
|
||
if (emote.code == emoteName) {
|
||
return emote.url();
|
||
}
|
||
}
|
||
return QString();
|
||
}
|
||
|
||
void BTTVProvider::onGlobalReplyFinished(QNetworkReply *reply) {
|
||
if (reply->error() != QNetworkReply::NoError) {
|
||
log("onGlobalReplyFinished",
|
||
QString("Network error: %1").arg(reply->errorString()),
|
||
LOG_ERROR);
|
||
reply->deleteLater();
|
||
return;
|
||
}
|
||
|
||
QByteArray data = reply->readAll();
|
||
parseGlobalResponse(data);
|
||
reply->deleteLater();
|
||
|
||
emit emotesLoaded();
|
||
}
|
||
|
||
void BTTVProvider::onCustomReplyFinished(QNetworkReply *reply) {
|
||
if (reply->error() != QNetworkReply::NoError) {
|
||
log("onCustomReplyFinished",
|
||
QString("Network error: %1").arg(reply->errorString()),
|
||
LOG_ERROR);
|
||
reply->deleteLater();
|
||
return;
|
||
}
|
||
|
||
QByteArray data = reply->readAll();
|
||
parseCustomResponse(data, m_lastUserId);
|
||
reply->deleteLater();
|
||
|
||
emit emotesLoaded();
|
||
}
|
||
|
||
void BTTVProvider::parseGlobalResponse(const QByteArray &data) {
|
||
QJsonParseError parseError;
|
||
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
|
||
|
||
if (parseError.error != QJsonParseError::NoError) {
|
||
log("parseGlobalResponse",
|
||
QString("JSON parse error: %1").arg(parseError.errorString()),
|
||
LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
if (!doc.isArray()) {
|
||
log("parseGlobalResponse", "Expected JSON array", LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
QJsonArray array = doc.array();
|
||
m_emotes.clear();
|
||
|
||
for (const QJsonValue &value : array) {
|
||
if (!value.isObject()) continue;
|
||
|
||
QJsonObject obj = value.toObject();
|
||
BTTVEmote emote;
|
||
emote.id = obj.value("id").toString();
|
||
emote.code = obj.value("code").toString();
|
||
|
||
if (!emote.id.isEmpty() && !emote.code.isEmpty()) {
|
||
m_emotes.append(emote);
|
||
}
|
||
}
|
||
|
||
log("parseGlobalResponse",
|
||
QString("Loaded %1 global emotes").arg(m_emotes.size()),
|
||
LOG_INFO);
|
||
}
|
||
|
||
void BTTVProvider::parseCustomResponse(const QByteArray &data, const QString &userId) {
|
||
QJsonParseError parseError;
|
||
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
|
||
|
||
if (parseError.error != QJsonParseError::NoError) {
|
||
log("parseCustomResponse",
|
||
QString("JSON parse error: %1").arg(parseError.errorString()),
|
||
LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
if (!doc.isObject()) {
|
||
log("parseCustomResponse", "Expected JSON object", LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
QJsonObject root = doc.object();
|
||
|
||
auto parseEmotesArray = [this](const QJsonArray &array) {
|
||
for (const QJsonValue &value : array) {
|
||
if (!value.isObject()) continue;
|
||
|
||
QJsonObject obj = value.toObject();
|
||
BTTVEmote emote;
|
||
emote.id = obj.value("id").toString();
|
||
emote.code = obj.value("code").toString();
|
||
|
||
if (!emote.id.isEmpty() && !emote.code.isEmpty()) {
|
||
m_emotes.append(emote);
|
||
}
|
||
}
|
||
};
|
||
|
||
// Очищаем только пользовательские эмодзи (можно оптимизировать)
|
||
// В реальном приложении нужно хранить отдельно глобальные и пользовательские
|
||
|
||
// Парсим channelEmotes
|
||
if (root.contains("channelEmotes") && root["channelEmotes"].isArray()) {
|
||
parseEmotesArray(root["channelEmotes"].toArray());
|
||
}
|
||
|
||
// Парсим sharedEmotes
|
||
if (root.contains("sharedEmotes") && root["sharedEmotes"].isArray()) {
|
||
parseEmotesArray(root["sharedEmotes"].toArray());
|
||
}
|
||
|
||
log("parseCustomResponse",
|
||
QString("Loaded %1 custom emotes for user %2").arg(m_emotes.size()).arg(userId),
|
||
LOG_INFO);
|
||
}
|
||
|
||
// SevenTVProvider implementation
|
||
SevenTVProvider::SevenTVProvider(QObject *parent)
|
||
: EmoteProvider(parent) {
|
||
}
|
||
|
||
SevenTVProvider::~SevenTVProvider() {
|
||
m_emotes.clear();
|
||
}
|
||
|
||
void SevenTVProvider::fetchGlobal() {
|
||
log("fetchGlobal", "Fetching global 7TV emotes", LOG_INFO);
|
||
|
||
QNetworkRequest request(QUrl(getBaseUrl() + "emote-sets/global"));
|
||
request.setHeader(QNetworkRequest::UserAgentHeader,
|
||
"Mozilla/5.0");
|
||
|
||
QNetworkReply *reply = m_networkManager->get(request);
|
||
connect(reply, &QNetworkReply::finished,
|
||
this, [this, reply]() { onGlobalReplyFinished(reply); });
|
||
}
|
||
|
||
void SevenTVProvider::fetchCustom(const QString &userId) {
|
||
if (userId.isEmpty()) {
|
||
log("fetchCustom", "User ID is empty", LOG_WARNING);
|
||
return;
|
||
}
|
||
|
||
log("fetchCustom", QString("Fetching custom 7TV emotes for user: %1").arg(userId), LOG_INFO);
|
||
m_lastUserId = userId;
|
||
|
||
QNetworkRequest request(QUrl(getBaseUrl() + "users/twitch/" + userId));
|
||
request.setHeader(QNetworkRequest::UserAgentHeader,
|
||
"Mozilla/5.0");
|
||
|
||
QNetworkReply *reply = m_networkManager->get(request);
|
||
connect(reply, &QNetworkReply::finished,
|
||
this, [this, reply]() { onCustomReplyFinished(reply); });
|
||
}
|
||
|
||
QString SevenTVProvider::getEmoteUrl(const QString &emoteName) {
|
||
for (const auto &emote : m_emotes) {
|
||
if (emote.code == emoteName) {
|
||
return emote.url;
|
||
}
|
||
}
|
||
return QString();
|
||
}
|
||
|
||
void SevenTVProvider::onGlobalReplyFinished(QNetworkReply *reply) {
|
||
if (reply->error() != QNetworkReply::NoError) {
|
||
log("onGlobalReplyFinished",
|
||
QString("Network error: %1").arg(reply->errorString()),
|
||
LOG_ERROR);
|
||
reply->deleteLater();
|
||
return;
|
||
}
|
||
|
||
QByteArray data = reply->readAll();
|
||
parseGlobalResponse(data);
|
||
reply->deleteLater();
|
||
|
||
emit emotesLoaded();
|
||
}
|
||
|
||
void SevenTVProvider::onCustomReplyFinished(QNetworkReply *reply) {
|
||
if (reply->error() != QNetworkReply::NoError) {
|
||
log("onCustomReplyFinished",
|
||
QString("Network error: %1").arg(reply->errorString()),
|
||
LOG_ERROR);
|
||
reply->deleteLater();
|
||
return;
|
||
}
|
||
|
||
QByteArray data = reply->readAll();
|
||
parseCustomResponse(data, m_lastUserId);
|
||
reply->deleteLater();
|
||
|
||
emit emotesLoaded();
|
||
}
|
||
|
||
void SevenTVProvider::parseGlobalResponse(const QByteArray &data) {
|
||
QJsonParseError parseError;
|
||
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
|
||
|
||
if (parseError.error != QJsonParseError::NoError) {
|
||
log("parseGlobalResponse",
|
||
QString("JSON parse error: %1").arg(parseError.errorString()),
|
||
LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
if (!doc.isObject()) {
|
||
log("parseGlobalResponse", "Expected JSON object", LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
QJsonObject root = doc.object();
|
||
m_emotes.clear();
|
||
|
||
if (!root.contains("emotes") || !root["emotes"].isArray()) {
|
||
log("parseGlobalResponse", "No emotes array found", LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
QJsonArray emotesArray = root["emotes"].toArray();
|
||
|
||
for (const QJsonValue &emoteValue : emotesArray) {
|
||
if (!emoteValue.isObject()) continue;
|
||
|
||
QJsonObject emoteObj = emoteValue.toObject();
|
||
SevenTVEmote emote;
|
||
|
||
emote.id = emoteObj.value("id").toString();
|
||
emote.code = emoteObj.value("name").toString();
|
||
|
||
// Парсим URL
|
||
if (emoteObj.contains("data") && emoteObj["data"].isObject()) {
|
||
QJsonObject dataObj = emoteObj["data"].toObject();
|
||
|
||
if (dataObj.contains("host") && dataObj["host"].isObject()) {
|
||
QJsonObject hostObj = dataObj["host"].toObject();
|
||
|
||
if (hostObj.contains("url")) {
|
||
QString baseUrl = "https:" + hostObj["url"].toString();
|
||
|
||
if (hostObj.contains("files") && hostObj["files"].isArray()) {
|
||
QJsonArray filesArray = hostObj["files"].toArray();
|
||
|
||
if (!filesArray.isEmpty() && filesArray[0].isObject()) {
|
||
QJsonObject fileObj = filesArray[0].toObject();
|
||
if (fileObj.contains("name")) {
|
||
emote.url = baseUrl + "/" + fileObj["name"].toString();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!emote.id.isEmpty() && !emote.code.isEmpty() && !emote.url.isEmpty()) {
|
||
m_emotes.append(emote);
|
||
}
|
||
}
|
||
|
||
log("parseGlobalResponse",
|
||
QString("Loaded %1 global emotes").arg(m_emotes.size()),
|
||
LOG_INFO);
|
||
}
|
||
|
||
void SevenTVProvider::parseCustomResponse(const QByteArray &data, const QString &userId) {
|
||
QJsonParseError parseError;
|
||
QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
|
||
|
||
if (parseError.error != QJsonParseError::NoError) {
|
||
log("parseCustomResponse",
|
||
QString("JSON parse error: %1").arg(parseError.errorString()),
|
||
LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
if (!doc.isObject()) {
|
||
log("parseCustomResponse", "Expected JSON object", LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
QJsonObject root = doc.object();
|
||
|
||
if (!root.contains("emote_set") || !root["emote_set"].isObject()) {
|
||
log("parseCustomResponse", "No emote_set found", LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
QJsonObject emoteSet = root["emote_set"].toObject();
|
||
|
||
if (!emoteSet.contains("emotes") || !emoteSet["emotes"].isArray()) {
|
||
log("parseCustomResponse", "No emotes array in emote_set", LOG_ERROR);
|
||
return;
|
||
}
|
||
|
||
QJsonArray emotesArray = emoteSet["emotes"].toArray();
|
||
|
||
for (const QJsonValue &emoteValue : emotesArray) {
|
||
if (!emoteValue.isObject()) continue;
|
||
|
||
QJsonObject emoteObj = emoteValue.toObject();
|
||
SevenTVEmote emote;
|
||
|
||
emote.id = emoteObj.value("id").toString();
|
||
emote.code = emoteObj.value("name").toString();
|
||
|
||
// Парсим URL (аналогично parseGlobalResponse)
|
||
if (emoteObj.contains("data") && emoteObj["data"].isObject()) {
|
||
QJsonObject dataObj = emoteObj["data"].toObject();
|
||
|
||
if (dataObj.contains("host") && dataObj["host"].isObject()) {
|
||
QJsonObject hostObj = dataObj["host"].toObject();
|
||
|
||
if (hostObj.contains("url")) {
|
||
QString baseUrl = "https:" + hostObj["url"].toString();
|
||
|
||
if (hostObj.contains("files") && hostObj["files"].isArray()) {
|
||
QJsonArray filesArray = hostObj["files"].toArray();
|
||
|
||
if (!filesArray.isEmpty() && filesArray[0].isObject()) {
|
||
QJsonObject fileObj = filesArray[0].toObject();
|
||
if (fileObj.contains("name")) {
|
||
emote.url = baseUrl + "/" + fileObj["name"].toString();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!emote.id.isEmpty() && !emote.code.isEmpty() && !emote.url.isEmpty()) {
|
||
m_emotes.append(emote);
|
||
}
|
||
}
|
||
|
||
log("parseCustomResponse",
|
||
QString("Loaded %1 custom emotes for user %2").arg(m_emotes.size()).arg(userId),
|
||
LOG_INFO);
|
||
}
|