Compare commits
4 Commits
39f0c447c1
...
v11.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f53bdcf96 | |||
| 76439727d2 | |||
| 1d3131ea8c | |||
| 47c0c7ed85 |
@@ -13,6 +13,9 @@
|
||||
*.sln
|
||||
*.vcxproj.*
|
||||
*.vcxproj.user
|
||||
*.iss
|
||||
*.rtf
|
||||
install.ico
|
||||
|
||||
# Собранные файлы и каталоги
|
||||
build-*/
|
||||
|
||||
@@ -87,7 +87,13 @@ void TAuth::handleNewConnection()
|
||||
connect(m_clientSocket, &QTcpSocket::readyRead,
|
||||
this, &TAuth::readClientData);
|
||||
connect(m_clientSocket, &QTcpSocket::disconnected,
|
||||
m_clientSocket, &QTcpSocket::deleteLater);
|
||||
this, [this]() {
|
||||
// Удаляем сокет после отключения
|
||||
if (m_clientSocket) {
|
||||
m_clientSocket->deleteLater();
|
||||
m_clientSocket = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +104,6 @@ void TAuth::readClientData()
|
||||
QByteArray requestData = m_clientSocket->readAll();
|
||||
QString request = QString::fromUtf8(requestData);
|
||||
|
||||
|
||||
QStringList lines = request.split("\r\n");
|
||||
if (lines.isEmpty()) return;
|
||||
|
||||
@@ -118,7 +123,6 @@ void TAuth::readClientData()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Обрабатываем обычный redirect
|
||||
if (document.startsWith("/redirect")) {
|
||||
handleRedirectRequest(document, m_clientSocket);
|
||||
@@ -237,6 +241,13 @@ void TAuth::handleRedirectRequest(const QString &request, QTcpSocket *socket)
|
||||
"<h2>Authorization Successful!</h2>\n"
|
||||
"<p>Token received. You can close this window.</p>\n"
|
||||
"</body>\n</html>";
|
||||
|
||||
// Отправляем ответ клиенту
|
||||
sendResponse(socket, html);
|
||||
|
||||
// Останавливаем сервер СРАЗУ
|
||||
QTimer::singleShot(100, this, &TAuth::stopServer);
|
||||
return;
|
||||
}
|
||||
// Проверяем наличие error_description
|
||||
else if (params.contains("error_description=")) {
|
||||
@@ -260,6 +271,13 @@ void TAuth::handleRedirectRequest(const QString &request, QTcpSocket *socket)
|
||||
"<h2>Authorization Successful!</h2>\n"
|
||||
"<p>Code received. You can close this window.</p>\n"
|
||||
"</body>\n</html>";
|
||||
|
||||
// Отправляем ответ клиенту
|
||||
sendResponse(socket, html);
|
||||
|
||||
// Останавливаем сервер СРАЗУ
|
||||
QTimer::singleShot(100, this, &TAuth::stopServer);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
html =
|
||||
@@ -269,13 +287,16 @@ void TAuth::handleRedirectRequest(const QString &request, QTcpSocket *socket)
|
||||
"<p>Try again or check your authorization URL.</p>\n"
|
||||
"</body>\n</html>";
|
||||
sendResponse(socket, html);
|
||||
|
||||
// Останавливаем сервер через 5 секунд если нет данных
|
||||
QTimer::singleShot(5000, this, &TAuth::stopServer);
|
||||
return;
|
||||
}
|
||||
|
||||
sendResponse(socket, html);
|
||||
|
||||
// Останавливаем сервер после обработки
|
||||
QTimer::singleShot(1000, this, &TAuth::stopServer);
|
||||
// Останавливаем сервер через 5 секунд для других случаев
|
||||
QTimer::singleShot(5000, this, &TAuth::stopServer);
|
||||
}
|
||||
|
||||
QString TAuth::extractParam(const QString ¶ms, const QString ¶mName)
|
||||
|
||||
+101
@@ -591,3 +591,104 @@ void TTwAPI::parseBadgesFromApi(const QString &jsonString, QVector<ChatBadge> &b
|
||||
badges.append(badge);
|
||||
}
|
||||
}
|
||||
|
||||
bool TTwAPI::validateTwitchToken(const QString &tokenName,
|
||||
const QString &tokenValue,
|
||||
int &daysValid)
|
||||
{
|
||||
daysValid = 0;
|
||||
|
||||
if (tokenValue.trimmed().isEmpty()) {
|
||||
toLog(1, "TTwAPI::validateTwitchToken", "Пустой токен: " + tokenName);
|
||||
return false;
|
||||
}
|
||||
|
||||
QUrl url("https://id.twitch.tv/oauth2/validate");
|
||||
QNetworkRequest request(url);
|
||||
|
||||
// Устанавливаем заголовки
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, "YourApp/1.0");
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
|
||||
// Формируем заголовок Authorization
|
||||
QString authHeader = "OAuth " + tokenValue;
|
||||
request.setRawHeader("Authorization", authHeader.toUtf8());
|
||||
|
||||
// Отправляем GET запрос
|
||||
QNetworkReply *reply = m_networkManager->get(request);
|
||||
|
||||
if (!reply) {
|
||||
toLog(2, "TTwAPI::validateTwitchToken", "Не удалось создать запрос для токена: " + tokenName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ожидание завершения запроса
|
||||
QEventLoop loop;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
|
||||
loop.exec();
|
||||
|
||||
// Проверяем статус ответа
|
||||
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
QByteArray responseData = reply->readAll();
|
||||
QString responseText = QString::fromUtf8(responseData);
|
||||
|
||||
// Обрабатываем ошибки сети
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QString errorMsg = QString("Ошибка сети: %1 - %2")
|
||||
.arg(reply->error())
|
||||
.arg(reply->errorString());
|
||||
toLog(2, "TTwAPI::validateTwitchToken", errorMsg);
|
||||
reply->deleteLater();
|
||||
return false;
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
// Обработка HTTP статусов
|
||||
if (statusCode == 200) {
|
||||
// Успешная валидация
|
||||
QJsonDocument doc = QJsonDocument::fromJson(responseData);
|
||||
if (!doc.isObject()) {
|
||||
toLog(2, "TTwAPI::validateTwitchToken", "Ошибка парсинга JSON для токена: " + tokenName);
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject jsonObj = doc.object();
|
||||
|
||||
// Извлекаем expires_in (в секундах)
|
||||
if (jsonObj.contains("expires_in") && jsonObj["expires_in"].isDouble()) {
|
||||
int expiresInSeconds = jsonObj["expires_in"].toInt();
|
||||
daysValid = qRound(expiresInSeconds / 86400.0); // Конвертируем секунды в дни
|
||||
|
||||
// Логируем информацию о токене
|
||||
QString logMsg = QString("Токен '%1' действителен. Осталось: %2 дней. "
|
||||
"Client ID: %3, Логин: %4")
|
||||
.arg(tokenName)
|
||||
.arg(daysValid)
|
||||
.arg(jsonObj.value("client_id").toString("N/A"))
|
||||
.arg(jsonObj.value("login").toString("N/A"));
|
||||
toLog(0, "TTwAPI::validateTwitchToken", logMsg);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
toLog(2, "TTwAPI::validateTwitchToken",
|
||||
QString("В ответе отсутствует expires_in для токена: %1").arg(tokenName));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (statusCode == 401) {
|
||||
// Невалидный токен
|
||||
toLog(2, "TTwAPI::validateTwitchToken",
|
||||
QString("Токен '%1' невалиден (HTTP 401)").arg(tokenName));
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// Другие HTTP ошибки
|
||||
QString errorMsg = QString("HTTP %1 для токена '%2': %3")
|
||||
.arg(statusCode)
|
||||
.arg(tokenName)
|
||||
.arg(responseText);
|
||||
toLog(2, "TTwAPI::validateTwitchToken", errorMsg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+551
-897
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -344,6 +344,8 @@ private slots:
|
||||
|
||||
void on_btnAUserName_clicked();
|
||||
|
||||
void on_btnRmWebService_clicked();
|
||||
|
||||
public slots:
|
||||
// Установка статуса подключения к Twitch
|
||||
void setTwitchConnected(bool connected);
|
||||
@@ -464,7 +466,8 @@ private:
|
||||
// Загрузка эмодзи
|
||||
void loadEmoties();
|
||||
void loadChatBadges();
|
||||
|
||||
void disconnectFromTwitch();
|
||||
void connectToTwitch();
|
||||
/**
|
||||
* @brief Инициализирует звуковые уведомления
|
||||
*/
|
||||
|
||||
+1762
-1769
File diff suppressed because it is too large
Load Diff
+84
-140
@@ -17,16 +17,15 @@ UserWidget::UserWidget(UserManager* userManager, QWidget *parent)
|
||||
connect(m_tableWidget, &QTableWidget::customContextMenuRequested,
|
||||
this, &UserWidget::showContextMenu);
|
||||
|
||||
setupContextMenu(); // Добавляем инициализацию контекстного меню
|
||||
// Подключаем сигналы если менеджер уже есть
|
||||
setupContextMenu();
|
||||
|
||||
if (m_userManager) {
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
// Таймер для периодического обновления
|
||||
m_refreshTimer = new QTimer(this);
|
||||
connect(m_refreshTimer, &QTimer::timeout, this, &UserWidget::onRefreshTimer);
|
||||
m_refreshTimer->start(30000); // 30 секунд
|
||||
m_refreshTimer->start(30000);
|
||||
|
||||
refreshData();
|
||||
}
|
||||
@@ -40,7 +39,6 @@ UserWidget::~UserWidget()
|
||||
|
||||
void UserWidget::setUserManager(UserManager* userManager)
|
||||
{
|
||||
// Отключаем старые соединения если были
|
||||
if (m_userManager) {
|
||||
disconnectSignals();
|
||||
}
|
||||
@@ -76,14 +74,26 @@ void UserWidget::disconnectSignals()
|
||||
|
||||
void UserWidget::setupUI()
|
||||
{
|
||||
// ------------------------------------------------------------
|
||||
// 1. Глобальные настройки самого виджета
|
||||
// ------------------------------------------------------------
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
setMinimumSize(400, 300); // гарантия, что виджет не сожмётся до нуля
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 2. Главный вертикальный layout
|
||||
// ------------------------------------------------------------
|
||||
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(5, 5, 5, 5);
|
||||
mainLayout->setSpacing(5);
|
||||
mainLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
|
||||
|
||||
// Панель инструментов
|
||||
// ------------------------------------------------------------
|
||||
// 3. Панель инструментов (фильтр, поиск, кнопка обновления)
|
||||
// ------------------------------------------------------------
|
||||
QHBoxLayout* toolbarLayout = new QHBoxLayout();
|
||||
toolbarLayout->setSpacing(5);
|
||||
|
||||
// Фильтр
|
||||
QLabel* filterLabel = new QLabel("Фильтр:", this);
|
||||
m_filterCombo = new QComboBox(this);
|
||||
m_filterCombo->addItem("Все зрители", FilterAll);
|
||||
@@ -91,70 +101,86 @@ void UserWidget::setupUI()
|
||||
m_filterCombo->addItem("VIP", FilterVIPs);
|
||||
m_filterCombo->addItem("Подписчики", FilterSubscribers);
|
||||
m_filterCombo->addItem("Активные (10 мин)", FilterActive);
|
||||
m_filterCombo->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
connect(m_filterCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
this, &UserWidget::onFilterChanged);
|
||||
|
||||
// Поиск
|
||||
QLabel* searchLabel = new QLabel("Поиск:", this);
|
||||
m_searchEdit = new QLineEdit(this);
|
||||
m_searchEdit->setPlaceholderText("Введите имя зрителя...");
|
||||
m_searchEdit->setPlaceholderText("Имя зрителя...");
|
||||
m_searchEdit->setClearButtonEnabled(true);
|
||||
m_searchEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
|
||||
connect(m_searchEdit, &QLineEdit::textChanged,
|
||||
this, &UserWidget::onSearchTextChanged);
|
||||
|
||||
// Кнопка обновления
|
||||
QPushButton* refreshBtn = new QPushButton("Обновить", this);
|
||||
refreshBtn->setFixedWidth(100);
|
||||
refreshBtn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
refreshBtn->setMaximumWidth(100);
|
||||
connect(refreshBtn, &QPushButton::clicked,
|
||||
this, &UserWidget::onRefreshButtonClicked);
|
||||
|
||||
toolbarLayout->addWidget(filterLabel);
|
||||
toolbarLayout->addWidget(m_filterCombo);
|
||||
toolbarLayout->addWidget(searchLabel);
|
||||
toolbarLayout->addWidget(m_searchEdit);
|
||||
toolbarLayout->addWidget(m_searchEdit, 1);
|
||||
toolbarLayout->addStretch();
|
||||
toolbarLayout->addWidget(refreshBtn);
|
||||
|
||||
// Таблица
|
||||
// ------------------------------------------------------------
|
||||
// 4. Таблица пользователей
|
||||
// ------------------------------------------------------------
|
||||
m_tableWidget = new QTableWidget(this);
|
||||
m_tableWidget->setColumnCount(6);
|
||||
m_tableWidget->setHorizontalHeaderLabels(
|
||||
QStringList() << "Имя" << "Сообщений" << "Статус"
|
||||
<< "VIP" << "Подписчик" << "Последняя активность");
|
||||
|
||||
// Настройка таблицы
|
||||
m_tableWidget->setSortingEnabled(true);
|
||||
m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_tableWidget->setAlternatingRowColors(true);
|
||||
m_tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_tableWidget->horizontalHeader()->setStretchLastSection(true);
|
||||
m_tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
|
||||
m_tableWidget->verticalHeader()->setVisible(false);
|
||||
|
||||
// Настройка ширины колонок
|
||||
m_tableWidget->setColumnWidth(0, 250); // Имя
|
||||
m_tableWidget->setColumnWidth(1, 80); // Сообщений
|
||||
m_tableWidget->setColumnWidth(2, 100); // Статус
|
||||
m_tableWidget->setColumnWidth(3, 50); // VIP
|
||||
m_tableWidget->setColumnWidth(4, 80); // Подписчик
|
||||
// Последняя колонка растягивается
|
||||
// Политика размеров таблицы – активное расширение
|
||||
m_tableWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
m_tableWidget->setMinimumHeight(200); // минимальная высота для отображения хоть чего-то
|
||||
|
||||
// Настройка ширины колонок (интерактивно, последняя растягивается)
|
||||
QHeaderView* header = m_tableWidget->horizontalHeader();
|
||||
header->setStretchLastSection(true);
|
||||
header->setSectionResizeMode(QHeaderView::Interactive);
|
||||
|
||||
// Начальные предпочтительные ширины
|
||||
m_tableWidget->setColumnWidth(0, 200);
|
||||
m_tableWidget->setColumnWidth(1, 80);
|
||||
m_tableWidget->setColumnWidth(2, 100);
|
||||
m_tableWidget->setColumnWidth(3, 50);
|
||||
m_tableWidget->setColumnWidth(4, 80);
|
||||
|
||||
connect(m_tableWidget, &QTableWidget::cellDoubleClicked,
|
||||
this, &UserWidget::onUserDoubleClicked);
|
||||
|
||||
// Статистика внизу
|
||||
// ------------------------------------------------------------
|
||||
// 5. Панель статистики
|
||||
// ------------------------------------------------------------
|
||||
QHBoxLayout* statsLayout = new QHBoxLayout();
|
||||
m_statsLabel = new QLabel(this);
|
||||
m_statsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
statsLayout->addWidget(m_statsLabel);
|
||||
statsLayout->addStretch();
|
||||
|
||||
// Добавляем всё на форму
|
||||
// ------------------------------------------------------------
|
||||
// 6. Сборка интерфейса
|
||||
// ------------------------------------------------------------
|
||||
mainLayout->addLayout(toolbarLayout);
|
||||
mainLayout->addWidget(m_tableWidget);
|
||||
mainLayout->addWidget(m_tableWidget, 1); // растягиваем таблицу с коэффициентом 1
|
||||
mainLayout->addLayout(statsLayout);
|
||||
|
||||
setLayout(mainLayout);
|
||||
|
||||
// Принудительно обновляем геометрию после установки layout
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void UserWidget::refreshData()
|
||||
@@ -176,7 +202,6 @@ void UserWidget::populateTable()
|
||||
|
||||
QVector<User*> users;
|
||||
|
||||
// Получаем пользователей в зависимости от фильтра
|
||||
switch (m_currentFilter) {
|
||||
case FilterAll:
|
||||
for (const User& user : m_userManager->getAllUsers()) {
|
||||
@@ -197,11 +222,9 @@ void UserWidget::populateTable()
|
||||
break;
|
||||
}
|
||||
|
||||
// Применяем поисковый фильтр
|
||||
if (!m_searchText.isEmpty()) {
|
||||
QString searchLower = m_searchText.toLower();
|
||||
QVector<User*> filteredUsers;
|
||||
|
||||
for (User* user : users) {
|
||||
if (user->displayName.toLower().contains(searchLower) ||
|
||||
user->login.toLower().contains(searchLower)) {
|
||||
@@ -211,22 +234,18 @@ void UserWidget::populateTable()
|
||||
users = filteredUsers;
|
||||
}
|
||||
|
||||
// Заполняем таблицу
|
||||
m_tableWidget->setRowCount(users.size());
|
||||
|
||||
for (int i = 0; i < users.size(); ++i) {
|
||||
User* user = users[i];
|
||||
|
||||
// Имя
|
||||
QTableWidgetItem* nameItem = new QTableWidgetItem(user->displayName);
|
||||
nameItem->setData(Qt::UserRole, user->id);
|
||||
m_tableWidget->setItem(i, 0, nameItem);
|
||||
|
||||
// Количество сообщений
|
||||
m_tableWidget->setItem(i, 1,
|
||||
new QTableWidgetItem(QString::number(user->messageCount)));
|
||||
|
||||
// Статус
|
||||
QString status;
|
||||
if (user->isModerator) status = "👑 Модератор";
|
||||
else if (user->isVIP) status = "⭐ VIP";
|
||||
@@ -234,43 +253,35 @@ void UserWidget::populateTable()
|
||||
else status = "👤 Зритель";
|
||||
m_tableWidget->setItem(i, 2, new QTableWidgetItem(status));
|
||||
|
||||
// VIP
|
||||
QTableWidgetItem* vipItem = new QTableWidgetItem(user->isVIP ? "Да" : "Нет");
|
||||
if (user->isVIP) {
|
||||
vipItem->setForeground(Qt::darkGreen);
|
||||
}
|
||||
if (user->isVIP) vipItem->setForeground(Qt::darkGreen);
|
||||
m_tableWidget->setItem(i, 3, vipItem);
|
||||
|
||||
// Подписчик
|
||||
QTableWidgetItem* subItem = new QTableWidgetItem(user->isSubscriber ? "Да" : "Нет");
|
||||
if (user->isSubscriber) {
|
||||
subItem->setForeground(Qt::darkBlue);
|
||||
}
|
||||
if (user->isSubscriber) subItem->setForeground(Qt::darkBlue);
|
||||
m_tableWidget->setItem(i, 4, subItem);
|
||||
|
||||
// Последняя активность
|
||||
QString lastActive;
|
||||
if (user->lastMessageTime.isValid()) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
qint64 secs = user->lastMessageTime.secsTo(now);
|
||||
|
||||
if (secs < 60) {
|
||||
lastActive = "Только что";
|
||||
} else if (secs < 3600) {
|
||||
lastActive = QString("%1 мин назад").arg(secs / 60);
|
||||
} else if (secs < 86400) {
|
||||
lastActive = QString("%1 ч назад").arg(secs / 3600);
|
||||
} else {
|
||||
lastActive = user->lastMessageTime.toString("dd.MM.yyyy hh:mm");
|
||||
}
|
||||
if (secs < 60) lastActive = "Только что";
|
||||
else if (secs < 3600) lastActive = QString("%1 мин назад").arg(secs / 60);
|
||||
else if (secs < 86400) lastActive = QString("%1 ч назад").arg(secs / 3600);
|
||||
else lastActive = user->lastMessageTime.toString("dd.MM.yyyy hh:mm");
|
||||
} else {
|
||||
lastActive = "Никогда";
|
||||
}
|
||||
m_tableWidget->setItem(i, 5, new QTableWidgetItem(lastActive));
|
||||
}
|
||||
|
||||
// Автоподгон ширины колонок после заполнения
|
||||
// Автоподгон ширины колонок с ограничением
|
||||
m_tableWidget->resizeColumnsToContents();
|
||||
for (int col = 0; col < m_tableWidget->columnCount() - 1; ++col) {
|
||||
if (m_tableWidget->columnWidth(col) > 300) {
|
||||
m_tableWidget->setColumnWidth(col, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UserWidget::updateStatistics()
|
||||
@@ -292,16 +303,13 @@ void UserWidget::addUserToTable(const User &user)
|
||||
int row = m_tableWidget->rowCount();
|
||||
m_tableWidget->insertRow(row);
|
||||
|
||||
// Имя
|
||||
QTableWidgetItem* nameItem = new QTableWidgetItem(user.displayName);
|
||||
nameItem->setData(Qt::UserRole, user.id);
|
||||
m_tableWidget->setItem(row, 0, nameItem);
|
||||
|
||||
// Количество сообщений
|
||||
m_tableWidget->setItem(row, 1,
|
||||
new QTableWidgetItem(QString::number(user.messageCount)));
|
||||
|
||||
// Статус
|
||||
QString status;
|
||||
if (user.isModerator) status = "👑 Модератор";
|
||||
else if (user.isVIP) status = "⭐ VIP";
|
||||
@@ -309,23 +317,15 @@ void UserWidget::addUserToTable(const User &user)
|
||||
else status = "👤 Зритель";
|
||||
m_tableWidget->setItem(row, 2, new QTableWidgetItem(status));
|
||||
|
||||
// VIP
|
||||
QTableWidgetItem* vipItem = new QTableWidgetItem(user.isVIP ? "Да" : "Нет");
|
||||
if (user.isVIP) {
|
||||
vipItem->setForeground(Qt::darkGreen);
|
||||
}
|
||||
if (user.isVIP) vipItem->setForeground(Qt::darkGreen);
|
||||
m_tableWidget->setItem(row, 3, vipItem);
|
||||
|
||||
// Подписчик
|
||||
QTableWidgetItem* subItem = new QTableWidgetItem(user.isSubscriber ? "Да" : "Нет");
|
||||
if (user.isSubscriber) {
|
||||
subItem->setForeground(Qt::darkBlue);
|
||||
}
|
||||
if (user.isSubscriber) subItem->setForeground(Qt::darkBlue);
|
||||
m_tableWidget->setItem(row, 4, subItem);
|
||||
|
||||
// Последняя активность
|
||||
QString lastActive = "Только что";
|
||||
m_tableWidget->setItem(row, 5, new QTableWidgetItem(lastActive));
|
||||
m_tableWidget->setItem(row, 5, new QTableWidgetItem("Только что"));
|
||||
|
||||
updateStatistics();
|
||||
}
|
||||
@@ -335,11 +335,9 @@ void UserWidget::updateUserInTable(const User &user)
|
||||
for (int row = 0; row < m_tableWidget->rowCount(); ++row) {
|
||||
QTableWidgetItem* item = m_tableWidget->item(row, 0);
|
||||
if (item && item->data(Qt::UserRole).toString() == user.id) {
|
||||
// Обновляем данные
|
||||
item->setText(user.displayName);
|
||||
|
||||
m_tableWidget->item(row, 1)->setText(
|
||||
QString::number(user.messageCount));
|
||||
m_tableWidget->item(row, 1)->setText(QString::number(user.messageCount));
|
||||
|
||||
QString status;
|
||||
if (user.isModerator) status = "👑 Модератор";
|
||||
@@ -349,34 +347,19 @@ void UserWidget::updateUserInTable(const User &user)
|
||||
m_tableWidget->item(row, 2)->setText(status);
|
||||
|
||||
m_tableWidget->item(row, 3)->setText(user.isVIP ? "Да" : "Нет");
|
||||
if (user.isVIP) {
|
||||
m_tableWidget->item(row, 3)->setForeground(Qt::darkGreen);
|
||||
} else {
|
||||
m_tableWidget->item(row, 3)->setForeground(Qt::black);
|
||||
}
|
||||
m_tableWidget->item(row, 3)->setForeground(user.isVIP ? Qt::darkGreen : Qt::black);
|
||||
|
||||
m_tableWidget->item(row, 4)->setText(user.isSubscriber ? "Да" : "Нет");
|
||||
if (user.isSubscriber) {
|
||||
m_tableWidget->item(row, 4)->setForeground(Qt::darkBlue);
|
||||
} else {
|
||||
m_tableWidget->item(row, 4)->setForeground(Qt::black);
|
||||
}
|
||||
m_tableWidget->item(row, 4)->setForeground(user.isSubscriber ? Qt::darkBlue : Qt::black);
|
||||
|
||||
// Обновляем время активности
|
||||
QString lastActive;
|
||||
if (user.lastMessageTime.isValid()) {
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
qint64 secs = user.lastMessageTime.secsTo(now);
|
||||
|
||||
if (secs < 60) {
|
||||
lastActive = "Только что";
|
||||
} else if (secs < 3600) {
|
||||
lastActive = QString("%1 мин назад").arg(secs / 60);
|
||||
} else if (secs < 86400) {
|
||||
lastActive = QString("%1 ч назад").arg(secs / 3600);
|
||||
} else {
|
||||
lastActive = user.lastMessageTime.toString("dd.MM hh:mm");
|
||||
}
|
||||
if (secs < 60) lastActive = "Только что";
|
||||
else if (secs < 3600) lastActive = QString("%1 мин назад").arg(secs / 60);
|
||||
else if (secs < 86400) lastActive = QString("%1 ч назад").arg(secs / 3600);
|
||||
else lastActive = user.lastMessageTime.toString("dd.MM hh:mm");
|
||||
}
|
||||
m_tableWidget->item(row, 5)->setText(lastActive);
|
||||
|
||||
@@ -388,7 +371,6 @@ void UserWidget::updateUserInTable(const User &user)
|
||||
|
||||
void UserWidget::removeUserFromTable(const QString &displayName)
|
||||
{
|
||||
// Ищем пользователя по displayName
|
||||
for (int row = 0; row < m_tableWidget->rowCount(); ++row) {
|
||||
QTableWidgetItem* item = m_tableWidget->item(row, 0);
|
||||
if (item && item->text() == displayName) {
|
||||
@@ -417,19 +399,12 @@ void UserWidget::onRefreshTimer()
|
||||
User* user = m_userManager->findUserById(userId);
|
||||
if (user && user->lastMessageTime.isValid()) {
|
||||
qint64 secs = user->lastMessageTime.secsTo(now);
|
||||
|
||||
QString lastActive;
|
||||
if (secs < 60) {
|
||||
lastActive = "Только что";
|
||||
} else if (secs < 3600) {
|
||||
lastActive = QString("%1 мин назад").arg(secs / 60);
|
||||
} else if (secs < 86400) {
|
||||
lastActive = QString("%1 ч назад").arg(secs / 3600);
|
||||
} else {
|
||||
lastActive = user->lastMessageTime.toString("dd.MM hh:mm");
|
||||
}
|
||||
if (secs < 60) lastActive = "Только что";
|
||||
else if (secs < 3600) lastActive = QString("%1 мин назад").arg(secs / 60);
|
||||
else if (secs < 86400) lastActive = QString("%1 ч назад").arg(secs / 3600);
|
||||
else lastActive = user->lastMessageTime.toString("dd.MM hh:mm");
|
||||
|
||||
// Проверяем, существует ли ячейка
|
||||
if (m_tableWidget->item(row, 5)) {
|
||||
m_tableWidget->item(row, 5)->setText(lastActive);
|
||||
}
|
||||
@@ -442,27 +417,17 @@ bool UserWidget::userMatchesFilter(const User* user) const
|
||||
{
|
||||
if (!user) return false;
|
||||
|
||||
// Проверка фильтра
|
||||
switch (m_currentFilter) {
|
||||
case FilterAll:
|
||||
break;
|
||||
case FilterModerators:
|
||||
if (!user->isModerator) return false;
|
||||
break;
|
||||
case FilterVIPs:
|
||||
if (!user->isVIP) return false;
|
||||
break;
|
||||
case FilterSubscribers:
|
||||
if (!user->isSubscriber) return false;
|
||||
break;
|
||||
case FilterAll: break;
|
||||
case FilterModerators: if (!user->isModerator) return false; break;
|
||||
case FilterVIPs: if (!user->isVIP) return false; break;
|
||||
case FilterSubscribers: if (!user->isSubscriber) return false; break;
|
||||
case FilterActive:
|
||||
// Активные за последние 10 минут
|
||||
if (user->lastMessageTime.secsTo(QDateTime::currentDateTime()) > 10*60)
|
||||
if (user->lastMessageTime.secsTo(QDateTime::currentDateTime()) > 10 * 60)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Проверка поиска
|
||||
if (!m_searchText.isEmpty()) {
|
||||
QString searchLower = m_searchText.toLower();
|
||||
if (!user->displayName.toLower().contains(searchLower) &&
|
||||
@@ -476,7 +441,6 @@ bool UserWidget::userMatchesFilter(const User* user) const
|
||||
|
||||
void UserWidget::onUserAdded(const User &user)
|
||||
{
|
||||
// Проверяем, проходит ли пользователь текущий фильтр и поиск
|
||||
if (userMatchesFilter(&user)) {
|
||||
addUserToTable(user);
|
||||
}
|
||||
@@ -484,22 +448,16 @@ void UserWidget::onUserAdded(const User &user)
|
||||
|
||||
void UserWidget::onUserUpdated(const User &user)
|
||||
{
|
||||
// Ищем пользователя в таблице
|
||||
int row = findRowByUserId(user.id);
|
||||
|
||||
if (row >= 0) {
|
||||
// Пользователь уже в таблице
|
||||
if (userMatchesFilter(&user)) {
|
||||
// Обновляем существующую запись
|
||||
updateUserInTable(user);
|
||||
} else {
|
||||
// Удаляем, так как пользователь больше не проходит фильтр
|
||||
removeUserFromTableByUserId(user.id);
|
||||
}
|
||||
} else {
|
||||
// Пользователя нет в таблице
|
||||
if (userMatchesFilter(&user)) {
|
||||
// Добавляем, так как пользователь теперь проходит фильтр
|
||||
addUserToTable(user);
|
||||
}
|
||||
}
|
||||
@@ -534,7 +492,6 @@ void UserWidget::onUserRemoved(const QString &userId, const QString &displayName
|
||||
void UserWidget::onUserDoubleClicked(int row, int column)
|
||||
{
|
||||
Q_UNUSED(column);
|
||||
|
||||
if (!m_userManager) return;
|
||||
|
||||
QTableWidgetItem* item = m_tableWidget->item(row, 0);
|
||||
@@ -551,9 +508,6 @@ void UserWidget::onUserDoubleClicked(int row, int column)
|
||||
<< "\nVIP:" << user->isVIP
|
||||
<< "\nПодписчик:" << user->isSubscriber
|
||||
<< "\nПоследнее сообщение:" << user->lastMessageTime.toString();
|
||||
|
||||
// Здесь можно открыть диалог с детальной информацией
|
||||
// или выполнить другие действия
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -610,38 +564,29 @@ void UserWidget::setupContextMenu()
|
||||
connect(m_infoAction, &QAction::triggered, this, &UserWidget::onShowUserInfo);
|
||||
}
|
||||
|
||||
// Добавляем обработчик контекстного меню:
|
||||
void UserWidget::showContextMenu(const QPoint &pos)
|
||||
{
|
||||
QTableWidgetItem* item = m_tableWidget->itemAt(pos);
|
||||
|
||||
if (!item) {
|
||||
return; // Клик был не по ячейке (например, по заголовку или пустой области)
|
||||
}
|
||||
if (!item) return;
|
||||
|
||||
int row = item->row();
|
||||
QTableWidgetItem* nameItem = m_tableWidget->item(row, 0);
|
||||
|
||||
if (nameItem) {
|
||||
m_selectedUserId = nameItem->data(Qt::UserRole).toString();
|
||||
m_selectedUserName = nameItem->text();
|
||||
|
||||
// Получаем данные пользователя
|
||||
User* user = m_userManager->findUserById(m_selectedUserId);
|
||||
if (user) {
|
||||
// Обновляем состояние пунктов меню
|
||||
m_setModAction->setEnabled(!user->isModerator);
|
||||
m_removeModAction->setEnabled(user->isModerator);
|
||||
m_setVIPAction->setEnabled(!user->isVIP);
|
||||
m_removeVIPAction->setEnabled(user->isVIP);
|
||||
|
||||
// Показываем меню в позиции клика
|
||||
m_contextMenu->exec(m_tableWidget->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем вспомогательный метод:
|
||||
User* UserWidget::getSelectedUser()
|
||||
{
|
||||
if (!m_selectedUserId.isEmpty()) {
|
||||
@@ -650,7 +595,6 @@ User* UserWidget::getSelectedUser()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Добавляем слоты для обработки действий меню:
|
||||
void UserWidget::onBanUser()
|
||||
{
|
||||
if (!m_selectedUserId.isEmpty() && !m_selectedUserName.isEmpty()) {
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ bool WebSocketClient::connectToServer(const QString &url)
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebSocketClient::connectToTwitchChat(const QString &oauthToken,
|
||||
bool WebSocketClient::connectToTwitchChat(const QString &oauthToken,
|
||||
const QString &nickname,
|
||||
const QString &channel)
|
||||
{
|
||||
@@ -65,7 +65,7 @@ void WebSocketClient::connectToTwitchChat(const QString &oauthToken,
|
||||
// URL для подключения к Twitch IRC через WebSocket
|
||||
QString url = "wss://irc-ws.chat.twitch.tv:443";
|
||||
|
||||
connectToServer(url);
|
||||
return connectToServer(url);
|
||||
}
|
||||
|
||||
void WebSocketClient::onConnectedInternal()
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ public:
|
||||
Q_INVOKABLE bool isConnected() const;
|
||||
|
||||
// Методы для Twitch чата
|
||||
Q_INVOKABLE void connectToTwitchChat(const QString &oauthToken, const QString &nickname, const QString &channel);
|
||||
Q_INVOKABLE bool connectToTwitchChat(const QString &oauthToken, const QString &nickname, const QString &channel);
|
||||
Q_INVOKABLE void joinChannel(const QString &channel);
|
||||
Q_INVOKABLE void sendChatMessage(const QString &channel, const QString &message);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user