mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Server/Client/Protocol] Let moderators clear banner cards (#7546)
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
b4e24f958a
commit
93309e01a2
12 changed files with 366 additions and 14 deletions
|
|
@ -18,6 +18,7 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QTableView>
|
#include <QTableView>
|
||||||
|
|
@ -29,7 +30,9 @@
|
||||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||||
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_card_art_rule_entry.pb.h>
|
#include <libcockatrice/protocol/pb/response_card_art_rule_entry.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/response_clear_blacklisted_banners.pb.h>
|
||||||
#include <libcockatrice/protocol/pending_command.h>
|
#include <libcockatrice/protocol/pending_command.h>
|
||||||
|
#include <libcockatrice/utility/qt_utils.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class QObject;
|
class QObject;
|
||||||
|
|
@ -172,6 +175,7 @@ void TabCardArtRules::setupUi()
|
||||||
addBtn = new QPushButton;
|
addBtn = new QPushButton;
|
||||||
removeBtn = new QPushButton;
|
removeBtn = new QPushButton;
|
||||||
refreshBtn = new QPushButton;
|
refreshBtn = new QPushButton;
|
||||||
|
clearBannedArtBtn = new QPushButton;
|
||||||
|
|
||||||
modeBox->addItems({"ALLOW", "DENY"});
|
modeBox->addItems({"ALLOW", "DENY"});
|
||||||
|
|
||||||
|
|
@ -188,10 +192,14 @@ void TabCardArtRules::setupUi()
|
||||||
form->addRow(tr("Mode:"), modeBox);
|
form->addRow(tr("Mode:"), modeBox);
|
||||||
form->addRow(tr("Reason:"), reasonEdit);
|
form->addRow(tr("Reason:"), reasonEdit);
|
||||||
|
|
||||||
|
// The bulk clear acts on every user, so keep it visibly apart from the
|
||||||
|
// rules this tab edits.
|
||||||
auto *buttons = new QHBoxLayout;
|
auto *buttons = new QHBoxLayout;
|
||||||
buttons->addWidget(addBtn);
|
buttons->addWidget(addBtn);
|
||||||
buttons->addWidget(removeBtn);
|
buttons->addWidget(removeBtn);
|
||||||
buttons->addWidget(refreshBtn);
|
buttons->addWidget(refreshBtn);
|
||||||
|
buttons->addStretch();
|
||||||
|
buttons->addWidget(clearBannedArtBtn);
|
||||||
|
|
||||||
auto *layout = new QVBoxLayout;
|
auto *layout = new QVBoxLayout;
|
||||||
layout->addLayout(form);
|
layout->addLayout(form);
|
||||||
|
|
@ -207,6 +215,8 @@ void TabCardArtRules::setupUi()
|
||||||
|
|
||||||
connect(refreshBtn, &QPushButton::clicked, this, &TabCardArtRules::refresh);
|
connect(refreshBtn, &QPushButton::clicked, this, &TabCardArtRules::refresh);
|
||||||
|
|
||||||
|
connect(clearBannedArtBtn, &QPushButton::clicked, this, &TabCardArtRules::clearBlacklistedBanners);
|
||||||
|
|
||||||
retranslateUi();
|
retranslateUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,6 +273,7 @@ void TabCardArtRules::retranslateUi()
|
||||||
addBtn->setText(tr("Add rule"));
|
addBtn->setText(tr("Add rule"));
|
||||||
removeBtn->setText(tr("Remove rule"));
|
removeBtn->setText(tr("Remove rule"));
|
||||||
refreshBtn->setText(tr("Refresh"));
|
refreshBtn->setText(tr("Refresh"));
|
||||||
|
clearBannedArtBtn->setText(tr("Clear blacklisted banners"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabCardArtRules::refresh()
|
void TabCardArtRules::refresh()
|
||||||
|
|
@ -299,4 +310,47 @@ void TabCardArtRules::removeSelected()
|
||||||
client->sendCommand(client->prepareModeratorCommand(cmd));
|
client->sendCommand(client->prepareModeratorCommand(cmd));
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TabCardArtRules::clearBlacklistedBanners()
|
||||||
|
{
|
||||||
|
if (!QtUtils::confirm(this, tr("Clear blacklisted banners"),
|
||||||
|
tr("Remove the banner card from every user whose current banner matches a DENY rule?\n\n"
|
||||||
|
"Users who are online keep showing their banner until they log in again."),
|
||||||
|
QMessageBox::No)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Command_ClearBlacklistedBanners cmd;
|
||||||
|
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||||
|
connect(pend, &PendingCommand::finished, this, &TabCardArtRules::clearBlacklistedBannersResponse);
|
||||||
|
client->sendCommand(pend);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabCardArtRules::clearBlacklistedBannersResponse(const Response &r)
|
||||||
|
{
|
||||||
|
switch (r.response_code()) {
|
||||||
|
case Response::RespOk:
|
||||||
|
break;
|
||||||
|
case Response::RespFunctionNotAllowed:
|
||||||
|
QMessageBox::warning(this, tr("Not supported"),
|
||||||
|
tr("This server does not support clearing blacklisted banners. It may be running an "
|
||||||
|
"older version."));
|
||||||
|
return;
|
||||||
|
case Response::RespInternalError:
|
||||||
|
QMessageBox::critical(this, tr("Error"),
|
||||||
|
tr("The server could not clear blacklisted banners because of a database error."));
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
QMessageBox::critical(this, tr("Error"),
|
||||||
|
tr("The server rejected the request: %1")
|
||||||
|
.arg(QString::fromStdString(Response_ResponseCode_Name(r.response_code()))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &resp = r.GetExtension(Response_ClearBlacklistedBanners::ext);
|
||||||
|
QMessageBox::information(this, tr("Banners cleared"),
|
||||||
|
tr("%n banner card(s) cleared.\n\n"
|
||||||
|
"Users who are online keep showing their banner until they log in again.",
|
||||||
|
"number of banners cleared", resp.cleared_count()));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,8 @@ private slots:
|
||||||
void addRule();
|
void addRule();
|
||||||
void removeSelected();
|
void removeSelected();
|
||||||
void refresh();
|
void refresh();
|
||||||
|
void clearBlacklistedBanners();
|
||||||
|
void clearBlacklistedBannersResponse(const Response &r);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AbstractClient *client;
|
AbstractClient *client;
|
||||||
|
|
@ -97,6 +99,7 @@ private:
|
||||||
QPushButton *addBtn;
|
QPushButton *addBtn;
|
||||||
QPushButton *removeBtn;
|
QPushButton *removeBtn;
|
||||||
QPushButton *refreshBtn;
|
QPushButton *refreshBtn;
|
||||||
|
QPushButton *clearBannedArtBtn;
|
||||||
|
|
||||||
QTableView *table;
|
QTableView *table;
|
||||||
CardArtRulesModel *tableModel;
|
CardArtRulesModel *tableModel;
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,14 @@
|
||||||
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_moderator_last_logins.pb.h>
|
#include <libcockatrice/protocol/pb/response_moderator_last_logins.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_remove_user_avatar.pb.h>
|
#include <libcockatrice/protocol/pb/response_remove_user_avatar.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/response_remove_user_banner.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_report_user_info.pb.h>
|
#include <libcockatrice/protocol/pb/response_report_user_info.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_reset_user_password.pb.h>
|
#include <libcockatrice/protocol/pb/response_reset_user_password.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_user_alts.pb.h>
|
#include <libcockatrice/protocol/pb/response_user_alts.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_user_sessions.pb.h>
|
#include <libcockatrice/protocol/pb/response_user_sessions.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||||
#include <libcockatrice/protocol/pending_command.h>
|
#include <libcockatrice/protocol/pending_command.h>
|
||||||
|
#include <libcockatrice/utility/qt_utils.h>
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|
@ -85,12 +87,16 @@ TabModeration::TabModeration(TabSupervisor *_tabSupervisor, AbstractClient *_cli
|
||||||
removeAvatarButton = new QPushButton;
|
removeAvatarButton = new QPushButton;
|
||||||
connect(removeAvatarButton, &QPushButton::clicked, this, &TabModeration::removeAvatar);
|
connect(removeAvatarButton, &QPushButton::clicked, this, &TabModeration::removeAvatar);
|
||||||
|
|
||||||
|
removeBannerButton = new QPushButton;
|
||||||
|
connect(removeBannerButton, &QPushButton::clicked, this, &TabModeration::removeBanner);
|
||||||
|
|
||||||
auto *topBar = new QHBoxLayout;
|
auto *topBar = new QHBoxLayout;
|
||||||
topBar->addWidget(searchEdit);
|
topBar->addWidget(searchEdit);
|
||||||
topBar->addWidget(investigateButton);
|
topBar->addWidget(investigateButton);
|
||||||
topBar->addStretch();
|
topBar->addStretch();
|
||||||
topBar->addWidget(resetPasswordButton);
|
topBar->addWidget(resetPasswordButton);
|
||||||
topBar->addWidget(removeAvatarButton);
|
topBar->addWidget(removeAvatarButton);
|
||||||
|
topBar->addWidget(removeBannerButton);
|
||||||
|
|
||||||
userInfoGroup = new QGroupBox;
|
userInfoGroup = new QGroupBox;
|
||||||
userInfoNameLabel = new QLabel;
|
userInfoNameLabel = new QLabel;
|
||||||
|
|
@ -186,6 +192,7 @@ void TabModeration::retranslateUi()
|
||||||
investigateButton->setText(tr("Investigate"));
|
investigateButton->setText(tr("Investigate"));
|
||||||
resetPasswordButton->setText(tr("Reset Password"));
|
resetPasswordButton->setText(tr("Reset Password"));
|
||||||
removeAvatarButton->setText(tr("Remove Avatar"));
|
removeAvatarButton->setText(tr("Remove Avatar"));
|
||||||
|
removeBannerButton->setText(tr("Remove Banner"));
|
||||||
refreshStaffButton->setText(tr("Refresh"));
|
refreshStaffButton->setText(tr("Refresh"));
|
||||||
|
|
||||||
userInfoGroup->setTitle(tr("User Info"));
|
userInfoGroup->setTitle(tr("User Info"));
|
||||||
|
|
@ -227,6 +234,7 @@ void TabModeration::clearUserData()
|
||||||
sessionsTable->setRowCount(0);
|
sessionsTable->setRowCount(0);
|
||||||
resetPasswordButton->setEnabled(false);
|
resetPasswordButton->setEnabled(false);
|
||||||
removeAvatarButton->setEnabled(false);
|
removeAvatarButton->setEnabled(false);
|
||||||
|
removeBannerButton->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabModeration::investigate(const QString &userName)
|
void TabModeration::investigate(const QString &userName)
|
||||||
|
|
@ -247,6 +255,7 @@ void TabModeration::investigateUser()
|
||||||
currentUser = userName;
|
currentUser = userName;
|
||||||
resetPasswordButton->setEnabled(true);
|
resetPasswordButton->setEnabled(true);
|
||||||
removeAvatarButton->setEnabled(true);
|
removeAvatarButton->setEnabled(true);
|
||||||
|
removeBannerButton->setEnabled(true);
|
||||||
altsTable->setRowCount(0);
|
altsTable->setRowCount(0);
|
||||||
sessionsTable->setRowCount(0);
|
sessionsTable->setRowCount(0);
|
||||||
requestUserInfo(userName);
|
requestUserInfo(userName);
|
||||||
|
|
@ -414,13 +423,11 @@ void TabModeration::resetPassword()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMessageBox::StandardButton choice =
|
if (!QtUtils::confirm(this, tr("Reset Password"),
|
||||||
QMessageBox::warning(this, tr("Reset Password"),
|
tr("Reset the password of %1? A temporary password will be generated and shown to you. "
|
||||||
tr("Reset the password of %1? A temporary password will be generated and shown to you. "
|
"The user must change it on their first login.")
|
||||||
"The user must change it on their first login.")
|
.arg(currentUser),
|
||||||
.arg(currentUser),
|
QMessageBox::No)) {
|
||||||
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel);
|
|
||||||
if (choice != QMessageBox::Ok) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,11 +458,9 @@ void TabModeration::removeAvatar()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMessageBox::StandardButton choice =
|
if (!QtUtils::confirm(this, tr("Remove Avatar"),
|
||||||
QMessageBox::warning(this, tr("Remove Avatar"),
|
tr("Remove the avatar of %1? The user will have to upload a new one.").arg(currentUser),
|
||||||
tr("Remove the avatar of %1? The user will have to upload a new one.").arg(currentUser),
|
QMessageBox::No)) {
|
||||||
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel);
|
|
||||||
if (choice != QMessageBox::Ok) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -477,3 +482,67 @@ void TabModeration::removeAvatarResponse(const Response &response)
|
||||||
QMessageBox::information(this, tr("Avatar removed"),
|
QMessageBox::information(this, tr("Avatar removed"),
|
||||||
tr("The avatar of %1 has been removed.").arg(QString::fromStdString(resp.user_name())));
|
tr("The avatar of %1 has been removed.").arg(QString::fromStdString(resp.user_name())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TabModeration::removeBanner()
|
||||||
|
{
|
||||||
|
if (currentUser.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QtUtils::confirm(this, tr("Remove Banner"),
|
||||||
|
tr("Remove the banner card of %1? The user will have to choose a new one.\n\n"
|
||||||
|
"Other players stop seeing the banner the next time %1 logs in.")
|
||||||
|
.arg(currentUser),
|
||||||
|
QMessageBox::No)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Command_RemoveUserBanner cmd;
|
||||||
|
cmd.set_user_name(currentUser.toStdString());
|
||||||
|
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||||
|
connect(pend, &PendingCommand::finished, this, &TabModeration::removeBannerResponse);
|
||||||
|
client->sendCommand(pend);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabModeration::removeBannerResponse(const Response &response)
|
||||||
|
{
|
||||||
|
const QString target = currentUser.isEmpty() ? tr("this user") : currentUser;
|
||||||
|
|
||||||
|
switch (response.response_code()) {
|
||||||
|
case Response::RespOk:
|
||||||
|
break;
|
||||||
|
case Response::RespNameNotFound:
|
||||||
|
QMessageBox::warning(this, tr("User not found"),
|
||||||
|
tr("The server does not know a user named %1.").arg(target));
|
||||||
|
return;
|
||||||
|
case Response::RespFunctionNotAllowed:
|
||||||
|
QMessageBox::warning(this, tr("Not supported"),
|
||||||
|
tr("This server does not support removing banner cards. It may be running an older "
|
||||||
|
"version."));
|
||||||
|
return;
|
||||||
|
case Response::RespInternalError:
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Error"),
|
||||||
|
tr("The server could not clear the banner card of %1 because of a database error.").arg(target));
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Error"),
|
||||||
|
tr("The server rejected the request for %1: %2")
|
||||||
|
.arg(target, QString::fromStdString(Response_ResponseCode_Name(response.response_code()))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Response_RemoveUserBanner &resp = response.GetExtension(Response_RemoveUserBanner::ext);
|
||||||
|
const QString removedUser = resp.user_name().empty() ? target : QString::fromStdString(resp.user_name());
|
||||||
|
if (!resp.had_banner()) {
|
||||||
|
QMessageBox::information(this, tr("No banner set"),
|
||||||
|
tr("%1 has no banner card set, so there was nothing to remove.").arg(removedUser));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::information(this, tr("Banner removed"),
|
||||||
|
tr("The banner card of %1 has been removed.\n\n"
|
||||||
|
"Other players stop seeing it the next time %1 logs in.")
|
||||||
|
.arg(removedUser));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class TabSupervisor;
|
||||||
/**
|
/**
|
||||||
* Staff investigation tool. Lets moderators look up a user's account data,
|
* Staff investigation tool. Lets moderators look up a user's account data,
|
||||||
* alternate accounts, login sessions, and staff login activity, and offers
|
* alternate accounts, login sessions, and staff login activity, and offers
|
||||||
* the password-reset and remove-avatar actions.
|
* the password-reset, remove-avatar, and remove-banner actions.
|
||||||
*/
|
*/
|
||||||
class TabModeration : public Tab
|
class TabModeration : public Tab
|
||||||
{
|
{
|
||||||
|
|
@ -44,6 +44,8 @@ private slots:
|
||||||
void resetPasswordResponse(const Response &response);
|
void resetPasswordResponse(const Response &response);
|
||||||
void removeAvatar();
|
void removeAvatar();
|
||||||
void removeAvatarResponse(const Response &response);
|
void removeAvatarResponse(const Response &response);
|
||||||
|
void removeBanner();
|
||||||
|
void removeBannerResponse(const Response &response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void requestUserInfo(const QString &userName);
|
void requestUserInfo(const QString &userName);
|
||||||
|
|
@ -60,6 +62,7 @@ private:
|
||||||
QPushButton *investigateButton;
|
QPushButton *investigateButton;
|
||||||
QPushButton *resetPasswordButton;
|
QPushButton *resetPasswordButton;
|
||||||
QPushButton *removeAvatarButton;
|
QPushButton *removeAvatarButton;
|
||||||
|
QPushButton *removeBannerButton;
|
||||||
|
|
||||||
QGroupBox *userInfoGroup;
|
QGroupBox *userInfoGroup;
|
||||||
QLabel *userInfoNameLabel;
|
QLabel *userInfoNameLabel;
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,8 @@ set(PROTO_FILES
|
||||||
response_report_user_info.proto
|
response_report_user_info.proto
|
||||||
response_moderator_last_logins.proto
|
response_moderator_last_logins.proto
|
||||||
response_remove_user_avatar.proto
|
response_remove_user_avatar.proto
|
||||||
|
response_remove_user_banner.proto
|
||||||
|
response_clear_blacklisted_banners.proto
|
||||||
response_reset_user_password.proto
|
response_reset_user_password.proto
|
||||||
response_user_alts.proto
|
response_user_alts.proto
|
||||||
response_user_sessions.proto
|
response_user_sessions.proto
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ message ModeratorCommand {
|
||||||
GET_MODERATOR_LAST_LOGINS = 1015;
|
GET_MODERATOR_LAST_LOGINS = 1015;
|
||||||
RESET_USER_PASSWORD = 1016;
|
RESET_USER_PASSWORD = 1016;
|
||||||
REMOVE_USER_AVATAR = 1017;
|
REMOVE_USER_AVATAR = 1017;
|
||||||
|
REMOVE_USER_BANNER = 1018;
|
||||||
|
CLEAR_BLACKLISTED_BANNERS = 1019;
|
||||||
REPORT_LIST = 1200;
|
REPORT_LIST = 1200;
|
||||||
REPORT_ASSIGN = 1201;
|
REPORT_ASSIGN = 1201;
|
||||||
REPORT_RESOLVE = 1202;
|
REPORT_RESOLVE = 1202;
|
||||||
|
|
@ -179,3 +181,16 @@ message Command_RemoveUserAvatar {
|
||||||
}
|
}
|
||||||
optional string user_name = 1;
|
optional string user_name = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Command_RemoveUserBanner {
|
||||||
|
extend ModeratorCommand {
|
||||||
|
optional Command_RemoveUserBanner ext = 1018;
|
||||||
|
}
|
||||||
|
optional string user_name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Command_ClearBlacklistedBanners {
|
||||||
|
extend ModeratorCommand {
|
||||||
|
optional Command_ClearBlacklistedBanners ext = 1019;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "response.proto";
|
||||||
|
|
||||||
|
message Response_ClearBlacklistedBanners {
|
||||||
|
extend Response {
|
||||||
|
optional Response_ClearBlacklistedBanners ext = 1222;
|
||||||
|
}
|
||||||
|
optional uint32 cleared_count = 1 [default = 0];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
syntax = "proto2";
|
||||||
|
import "response.proto";
|
||||||
|
|
||||||
|
message Response_RemoveUserBanner {
|
||||||
|
extend Response {
|
||||||
|
optional Response_RemoveUserBanner ext = 1221;
|
||||||
|
}
|
||||||
|
optional string user_name = 1;
|
||||||
|
// false when the user exists but had no banner card set, so nothing was cleared
|
||||||
|
optional bool had_banner = 2 [default = true];
|
||||||
|
}
|
||||||
|
|
@ -2077,6 +2077,108 @@ bool Servatrice_DatabaseInterface::removeUserAvatar(const QString &userName)
|
||||||
return query->numRowsAffected() > 0;
|
return query->numRowsAffected() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Servatrice_DatabaseInterface::RemoveUserBannerResult
|
||||||
|
Servatrice_DatabaseInterface::removeUserBanner(const QString &userName)
|
||||||
|
{
|
||||||
|
if (!checkSql()) {
|
||||||
|
return RemoveUserBannerResult::DatabaseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The UPDATE alone cannot tell a missing user from a user without a banner,
|
||||||
|
// so read the row first and let the moderator know which case they hit.
|
||||||
|
QSqlQuery *lookupQuery = prepareQuery("SELECT card_art_params FROM {prefix}_users WHERE name = :user_name");
|
||||||
|
lookupQuery->bindValue(":user_name", userName);
|
||||||
|
if (!execSqlQuery(lookupQuery)) {
|
||||||
|
return RemoveUserBannerResult::DatabaseError;
|
||||||
|
}
|
||||||
|
if (!lookupQuery->next()) {
|
||||||
|
return RemoveUserBannerResult::NoSuchUser;
|
||||||
|
}
|
||||||
|
if (lookupQuery->value(0).toString().isEmpty()) {
|
||||||
|
return RemoveUserBannerResult::NothingToRemove;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET card_art_params = NULL WHERE name = :user_name");
|
||||||
|
query->bindValue(":user_name", userName);
|
||||||
|
if (!execSqlQuery(query)) {
|
||||||
|
return RemoveUserBannerResult::DatabaseError;
|
||||||
|
}
|
||||||
|
return RemoveUserBannerResult::Removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// isCardArtAllowed() compares against the rule table with the database's
|
||||||
|
// case-insensitive collation, so the sweep has to fold case the same way or it
|
||||||
|
// leaves behind banners the server already treats as blacklisted.
|
||||||
|
static QPair<QString, QString> bannerKey(const QString &cardName, const QString &cardProviderId)
|
||||||
|
{
|
||||||
|
return {cardName.toCaseFolded(), cardProviderId.toCaseFolded()};
|
||||||
|
}
|
||||||
|
|
||||||
|
int Servatrice_DatabaseInterface::clearBlacklistedBanners()
|
||||||
|
{
|
||||||
|
if (!checkSql()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery *ruleQuery = prepareQuery("SELECT card_name, card_provider_id FROM {prefix}_card_art_name_rules "
|
||||||
|
"WHERE mode = 'DENY'");
|
||||||
|
if (!execSqlQuery(ruleQuery)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QPair<QString, QString>> denied;
|
||||||
|
while (ruleQuery->next()) {
|
||||||
|
denied.insert(bannerKey(ruleQuery->value(0).toString(), ruleQuery->value(1).toString()));
|
||||||
|
}
|
||||||
|
if (denied.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery *userQuery =
|
||||||
|
prepareQuery("SELECT id, card_art_params FROM {prefix}_users WHERE card_art_params IS NOT NULL");
|
||||||
|
if (!execSqlQuery(userQuery)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QVariant> matchingIds;
|
||||||
|
while (userQuery->next()) {
|
||||||
|
const QJsonDocument doc = QJsonDocument::fromJson(userQuery->value(1).toString().toUtf8());
|
||||||
|
if (!doc.isObject()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const QJsonObject obj = doc.object();
|
||||||
|
if (denied.contains(bannerKey(obj.value("card_name").toString(), obj.value("card_provider_id").toString()))) {
|
||||||
|
matchingIds.append(userQuery->value(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingIds.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chunk the UPDATE so very large user tables do not exceed driver placeholder limits.
|
||||||
|
int cleared = 0;
|
||||||
|
constexpr int BATCH_SIZE = 500;
|
||||||
|
for (int i = 0; i < matchingIds.size(); i += BATCH_SIZE) {
|
||||||
|
const QList<QVariant> batch = matchingIds.mid(i, BATCH_SIZE);
|
||||||
|
QStringList placeholders;
|
||||||
|
placeholders.reserve(batch.size());
|
||||||
|
for (int j = 0; j < batch.size(); ++j) {
|
||||||
|
placeholders.append(QString(":id%1").arg(j));
|
||||||
|
}
|
||||||
|
QSqlQuery *clearQuery = prepareQuery(
|
||||||
|
QString("UPDATE {prefix}_users SET card_art_params = NULL WHERE id IN (%1)").arg(placeholders.join(",")));
|
||||||
|
for (int j = 0; j < batch.size(); ++j) {
|
||||||
|
clearQuery->bindValue(placeholders.at(j), batch.at(j));
|
||||||
|
}
|
||||||
|
if (!execSqlQuery(clearQuery)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
cleared += clearQuery->numRowsAffected();
|
||||||
|
}
|
||||||
|
return cleared;
|
||||||
|
}
|
||||||
|
|
||||||
QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory(const QString &user,
|
QList<ServerInfo_ChatMessage> Servatrice_DatabaseInterface::getMessageLogHistory(const QString &user,
|
||||||
const QString &ipaddress,
|
const QString &ipaddress,
|
||||||
const QString &gamename,
|
const QString &gamename,
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,18 @@ public:
|
||||||
};
|
};
|
||||||
UptimeSnapshot getLatestUptimeSnapshot(int serverId);
|
UptimeSnapshot getLatestUptimeSnapshot(int serverId);
|
||||||
bool removeUserAvatar(const QString &userName);
|
bool removeUserAvatar(const QString &userName);
|
||||||
|
|
||||||
|
// Outcome of removeUserBanner(), so the caller can tell a missing user apart
|
||||||
|
// from a user who simply has no banner card set.
|
||||||
|
enum class RemoveUserBannerResult
|
||||||
|
{
|
||||||
|
Removed,
|
||||||
|
NothingToRemove,
|
||||||
|
NoSuchUser,
|
||||||
|
DatabaseError,
|
||||||
|
};
|
||||||
|
RemoveUserBannerResult removeUserBanner(const QString &userName);
|
||||||
|
int clearBlacklistedBanners();
|
||||||
bool isCardArtAllowed(const QString &cardName, const QString &cardProviderId) override;
|
bool isCardArtAllowed(const QString &cardName, const QString &cardProviderId) override;
|
||||||
bool addForgotPassword(const QString &user);
|
bool addForgotPassword(const QString &user);
|
||||||
bool removeForgotPassword(const QString &user) override;
|
bool removeForgotPassword(const QString &user) override;
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@
|
||||||
#include <libcockatrice/protocol/pb/event_user_message.pb.h>
|
#include <libcockatrice/protocol/pb/event_user_message.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_ban_history.pb.h>
|
#include <libcockatrice/protocol/pb/response_ban_history.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_card_art_rule_entry.pb.h>
|
#include <libcockatrice/protocol/pb/response_card_art_rule_entry.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/response_clear_blacklisted_banners.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
|
#include <libcockatrice/protocol/pb/response_deck_download.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_deck_list.pb.h>
|
#include <libcockatrice/protocol/pb/response_deck_list.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_deck_share_create.pb.h>
|
#include <libcockatrice/protocol/pb/response_deck_share_create.pb.h>
|
||||||
|
|
@ -140,6 +141,7 @@
|
||||||
#include <libcockatrice/protocol/pb/response_password_salt.pb.h>
|
#include <libcockatrice/protocol/pb/response_password_salt.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_register.pb.h>
|
#include <libcockatrice/protocol/pb/response_register.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_remove_user_avatar.pb.h>
|
#include <libcockatrice/protocol/pb/response_remove_user_avatar.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/response_remove_user_banner.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_replay_download.pb.h>
|
#include <libcockatrice/protocol/pb/response_replay_download.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_replay_download_by_game_id.pb.h>
|
#include <libcockatrice/protocol/pb/response_replay_download_by_game_id.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/response_replay_get_code.pb.h>
|
#include <libcockatrice/protocol/pb/response_replay_get_code.pb.h>
|
||||||
|
|
@ -484,6 +486,10 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo
|
||||||
return cmdGetModeratorLastLogins(cmd.GetExtension(Command_GetModeratorLastLogins::ext), rc);
|
return cmdGetModeratorLastLogins(cmd.GetExtension(Command_GetModeratorLastLogins::ext), rc);
|
||||||
case ModeratorCommand::REMOVE_USER_AVATAR:
|
case ModeratorCommand::REMOVE_USER_AVATAR:
|
||||||
return cmdRemoveUserAvatar(cmd.GetExtension(Command_RemoveUserAvatar::ext), rc);
|
return cmdRemoveUserAvatar(cmd.GetExtension(Command_RemoveUserAvatar::ext), rc);
|
||||||
|
case ModeratorCommand::REMOVE_USER_BANNER:
|
||||||
|
return cmdRemoveUserBanner(cmd.GetExtension(Command_RemoveUserBanner::ext), rc);
|
||||||
|
case ModeratorCommand::CLEAR_BLACKLISTED_BANNERS:
|
||||||
|
return cmdClearBlacklistedBanners(cmd.GetExtension(Command_ClearBlacklistedBanners::ext), rc);
|
||||||
default:
|
default:
|
||||||
return Response::RespFunctionNotAllowed;
|
return Response::RespFunctionNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
@ -2691,6 +2697,67 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRemoveUserAvatar(const
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode AbstractServerSocketInterface::cmdRemoveUserBanner(const Command_RemoveUserBanner &cmd,
|
||||||
|
ResponseContainer &rc)
|
||||||
|
{
|
||||||
|
if (!sqlInterface->checkSql()) {
|
||||||
|
return Response::RespInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString userName = nameFromStdString(cmd.user_name()).simplified();
|
||||||
|
if (userName.isEmpty()) {
|
||||||
|
return Response::RespContextError;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hadBanner = true;
|
||||||
|
switch (sqlInterface->removeUserBanner(userName)) {
|
||||||
|
case Servatrice_DatabaseInterface::RemoveUserBannerResult::Removed:
|
||||||
|
break;
|
||||||
|
case Servatrice_DatabaseInterface::RemoveUserBannerResult::NothingToRemove:
|
||||||
|
hadBanner = false;
|
||||||
|
break;
|
||||||
|
case Servatrice_DatabaseInterface::RemoveUserBannerResult::NoSuchUser:
|
||||||
|
return Response::RespNameNotFound;
|
||||||
|
case Servatrice_DatabaseInterface::RemoveUserBannerResult::DatabaseError:
|
||||||
|
default:
|
||||||
|
return Response::RespInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hadBanner) {
|
||||||
|
sqlInterface->addAuditRecord(userName, this->getAddress(), QString::fromStdString(userInfo->clientid()),
|
||||||
|
"REMOVE_USER_BANNER", "Moderator removed user banner card", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Response_RemoveUserBanner *re = new Response_RemoveUserBanner;
|
||||||
|
re->set_user_name(userName.toStdString());
|
||||||
|
re->set_had_banner(hadBanner);
|
||||||
|
rc.setResponseExtension(re);
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::ResponseCode
|
||||||
|
AbstractServerSocketInterface::cmdClearBlacklistedBanners(const Command_ClearBlacklistedBanners & /* cmd */,
|
||||||
|
ResponseContainer &rc)
|
||||||
|
{
|
||||||
|
if (!sqlInterface->checkSql()) {
|
||||||
|
return Response::RespInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int cleared = sqlInterface->clearBlacklistedBanners();
|
||||||
|
if (cleared < 0) {
|
||||||
|
return Response::RespInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlInterface->addAuditRecord(QString::fromStdString(userInfo->name()), this->getAddress(),
|
||||||
|
QString::fromStdString(userInfo->clientid()), "CLEAR_BLACKLISTED_BANNERS",
|
||||||
|
QString("Moderator cleared %1 blacklisted banner(s)").arg(cleared), true);
|
||||||
|
|
||||||
|
Response_ClearBlacklistedBanners *re = new Response_ClearBlacklistedBanners;
|
||||||
|
re->set_cleared_count(static_cast<unsigned int>(cleared));
|
||||||
|
rc.setResponseExtension(re);
|
||||||
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
void AbstractServerSocketInterface::sendPendingReportNotifications(ResponseContainer &rc)
|
void AbstractServerSocketInterface::sendPendingReportNotifications(ResponseContainer &rc)
|
||||||
{
|
{
|
||||||
if (!sqlInterface->checkSql()) {
|
if (!sqlInterface->checkSql()) {
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@ class Command_AccountPassword;
|
||||||
class Command_Activate;
|
class Command_Activate;
|
||||||
class Command_AddCardArtRule;
|
class Command_AddCardArtRule;
|
||||||
class Command_AdjustMod;
|
class Command_AdjustMod;
|
||||||
|
class Command_ClearBlacklistedBanners;
|
||||||
class Command_ForceActivateUser;
|
class Command_ForceActivateUser;
|
||||||
class Command_ForgotPasswordChallenge;
|
class Command_ForgotPasswordChallenge;
|
||||||
class Command_ForgotPasswordRequest;
|
class Command_ForgotPasswordRequest;
|
||||||
|
|
@ -90,6 +91,7 @@ class Command_ListCardArtRules;
|
||||||
class Command_Register;
|
class Command_Register;
|
||||||
class Command_RemoveCardArtRule;
|
class Command_RemoveCardArtRule;
|
||||||
class Command_RemoveUserAvatar;
|
class Command_RemoveUserAvatar;
|
||||||
|
class Command_RemoveUserBanner;
|
||||||
class Command_Report;
|
class Command_Report;
|
||||||
class Command_ReportAddComment;
|
class Command_ReportAddComment;
|
||||||
class Command_ReportAssign;
|
class Command_ReportAssign;
|
||||||
|
|
@ -236,6 +238,9 @@ private:
|
||||||
Response::ResponseCode cmdGetModeratorLastLogins(const Command_GetModeratorLastLogins &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdGetModeratorLastLogins(const Command_GetModeratorLastLogins &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdResetUserPassword(const Command_ResetUserPassword &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdResetUserPassword(const Command_ResetUserPassword &cmd, ResponseContainer &rc);
|
||||||
Response::ResponseCode cmdRemoveUserAvatar(const Command_RemoveUserAvatar &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdRemoveUserAvatar(const Command_RemoveUserAvatar &cmd, ResponseContainer &rc);
|
||||||
|
Response::ResponseCode cmdRemoveUserBanner(const Command_RemoveUserBanner &cmd, ResponseContainer &rc);
|
||||||
|
Response::ResponseCode cmdClearBlacklistedBanners(const Command_ClearBlacklistedBanners &cmd,
|
||||||
|
ResponseContainer &rc);
|
||||||
|
|
||||||
Response::ResponseCode cmdGetServerStats(const Command_GetServerStats &cmd, ResponseContainer &rc);
|
Response::ResponseCode cmdGetServerStats(const Command_GetServerStats &cmd, ResponseContainer &rc);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue