mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 09:33:25 -08:00
[Server] Add room chat channels with per-channel fan-out and history (#7476)
* [Server] Add room chat channels with per-channel fan-out and history * Fixup: redact all channel histories on message removal removeSaidMessages only redacted the main history, so banned/warned content stayed visible to late joiners on every other channel. Drop the unused channelId parameter and redact up to amount messages per channel across the whole channelChatHistory map. * [Server] Validate room channel ids and fix catalog schema per review - servatrice.sql: expand rooms_channels access_level enum to full set to match 0037->0038 migration - servatrice.cpp: skip empty, reserved (__main__) and duplicate channel ids when loading from ini and DB with qWarning - server_protocolhandler.cpp: clarify catalog replay comment (filtering happens in moderator stack) - server_room: drop duplicate channelIndex, linear search preserves catalog order * [Server] Fix server_room.cpp formatting per clang-format-18 --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de> Co-authored-by: BruebachL <bruebachl@cockatrice.example>
This commit is contained in:
parent
be38e8d429
commit
17d2e8d86e
11 changed files with 281 additions and 34 deletions
21
servatrice/migrations/servatrice_0037_to_0038.sql
Normal file
21
servatrice/migrations/servatrice_0037_to_0038.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
-- Servatrice db migration from version 37 to version 38
|
||||
|
||||
-- Room chat channels.
|
||||
--
|
||||
-- A room now offers a fixed catalog of named message streams in addition to the
|
||||
-- implicit Main channel. Public channels are broadcast to every room member;
|
||||
-- restricted channels are only delivered to (and sendable by) members whose
|
||||
-- privilege level meets the channel's access level.
|
||||
|
||||
-- 1. Catalog table, mirroring the config-driven channels of no-DB servers.
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_rooms_channels` (
|
||||
`id_room` int(7) unsigned NOT NULL,
|
||||
`id_server` tinyint(3) NOT NULL DEFAULT 1,
|
||||
`channel_id` varchar(50) NOT NULL,
|
||||
`display_name` varchar(50) NOT NULL,
|
||||
`access_level` ENUM('public','registered','judge','moderator','admin','developer') NOT NULL DEFAULT 'public',
|
||||
PRIMARY KEY (`id_room`, `id_server`, `channel_id`),
|
||||
FOREIGN KEY(`id_room`) REFERENCES `cockatrice_rooms`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
UPDATE cockatrice_schema_version SET version=38 WHERE version=37;
|
||||
|
|
@ -332,6 +332,22 @@ roomlist\1\game_types\1\name="GameType1"
|
|||
roomlist\1\game_types\2\name="GameType2"
|
||||
roomlist\1\game_types\3\name="GameType3"
|
||||
|
||||
; Optional chat channels for room number 1, in addition to the implicit Main
|
||||
; channel every room has. Each channel needs a stable ASCII id, a display name
|
||||
; (server data, like the room name) and an access level. Supported levels, from
|
||||
; least to most restrictive, are: public, registered, judge, moderator, admin
|
||||
; and developer. A channel is readable and writable by every member whose own
|
||||
; privilege level is at least the channel's level -- so a moderator channel is
|
||||
; also reachable by admins and developers, and an admin channel by developers.
|
||||
; Public channels are visible to every room member.
|
||||
roomlist\1\channels\size=2
|
||||
roomlist\1\channels\1\id=looking-for-players
|
||||
roomlist\1\channels\1\displayname="Looking for players"
|
||||
roomlist\1\channels\1\access=public
|
||||
roomlist\1\channels\2\id=moderators
|
||||
roomlist\1\channels\2\displayname="Moderators"
|
||||
roomlist\1\channels\2\access=moderator
|
||||
|
||||
|
||||
[game]
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` (
|
|||
PRIMARY KEY (`version`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
INSERT INTO cockatrice_schema_version VALUES(37);
|
||||
INSERT INTO cockatrice_schema_version VALUES(38);
|
||||
|
||||
-- users and user data tables
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_users` (
|
||||
|
|
@ -158,6 +158,16 @@ CREATE TABLE IF NOT EXISTS `cockatrice_rooms_gametypes` (
|
|||
FOREIGN KEY(`id_room`) REFERENCES `cockatrice_rooms`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_rooms_channels` (
|
||||
`id_room` int(7) unsigned NOT NULL,
|
||||
`id_server` tinyint(3) NOT NULL DEFAULT 1,
|
||||
`channel_id` varchar(50) NOT NULL,
|
||||
`display_name` varchar(50) NOT NULL,
|
||||
`access_level` ENUM('public','registered','judge','moderator','admin','developer') NOT NULL DEFAULT 'public',
|
||||
PRIMARY KEY (`id_room`, `id_server`, `channel_id`),
|
||||
FOREIGN KEY(`id_room`) REFERENCES `cockatrice_rooms`(`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- games
|
||||
CREATE TABLE IF NOT EXISTS `cockatrice_games` (
|
||||
`room_name` varchar(255) NOT NULL,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QReadLocker>
|
||||
#include <QSet>
|
||||
#include <QSharedPointer>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
|
|
@ -65,6 +66,7 @@
|
|||
#include <libcockatrice/protocol/pb/event_connection_closed.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_server_message.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_server_shutdown.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_room_channel.pb.h>
|
||||
#include <new>
|
||||
#include <qlogging.h>
|
||||
#include <qnamespace.h>
|
||||
|
|
@ -249,6 +251,34 @@ Servatrice::~Servatrice()
|
|||
prepareDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Maps a configured channel access level (database column or ini value)
|
||||
* onto the ServerInfo_RoomChannel privilege ladder.
|
||||
*
|
||||
* Unknown keywords fall back to Public so a typo never hides a channel that was
|
||||
* meant to be broadly readable.
|
||||
*/
|
||||
static ServerInfo_RoomChannel::AccessLevel roomChannelAccessFromKey(const QString &key)
|
||||
{
|
||||
const QString level = key.trimmed().toLower();
|
||||
if (level == "developer") {
|
||||
return ServerInfo_RoomChannel::Developer;
|
||||
}
|
||||
if (level == "admin") {
|
||||
return ServerInfo_RoomChannel::Admin;
|
||||
}
|
||||
if (level == "moderator") {
|
||||
return ServerInfo_RoomChannel::Moderator;
|
||||
}
|
||||
if (level == "judge") {
|
||||
return ServerInfo_RoomChannel::Judge;
|
||||
}
|
||||
if (level == "registered") {
|
||||
return ServerInfo_RoomChannel::Registered;
|
||||
}
|
||||
return ServerInfo_RoomChannel::Public;
|
||||
}
|
||||
|
||||
bool Servatrice::initServer()
|
||||
{
|
||||
|
||||
|
|
@ -360,10 +390,41 @@ bool Servatrice::initServer()
|
|||
while (query2->next()) {
|
||||
gameTypes.append(query2->value(0).toString());
|
||||
}
|
||||
|
||||
QSqlQuery *query3 = servatriceDatabaseInterface->prepareQuery(
|
||||
"select channel_id, display_name, access_level from {prefix}_rooms_channels where id_room = :id_room "
|
||||
"AND id_server = :id_server order by channel_id asc");
|
||||
query3->bindValue(":id_server", serverId);
|
||||
query3->bindValue(":id_room", query->value(0).toInt());
|
||||
servatriceDatabaseInterface->execSqlQuery(query3);
|
||||
QList<ServerInfo_RoomChannel> channels;
|
||||
QSet<QString> seenChannelIds;
|
||||
while (query3->next()) {
|
||||
const QString channelId = query3->value(0).toString();
|
||||
if (channelId.isEmpty()) {
|
||||
qWarning() << "Skipping room channel with empty id";
|
||||
continue;
|
||||
}
|
||||
if (Server_Room::isMainChannel(channelId)) {
|
||||
qWarning() << "Skipping room channel with reserved id" << channelId;
|
||||
continue;
|
||||
}
|
||||
if (seenChannelIds.contains(channelId)) {
|
||||
qWarning() << "Skipping duplicate room channel id" << channelId;
|
||||
continue;
|
||||
}
|
||||
seenChannelIds.insert(channelId);
|
||||
ServerInfo_RoomChannel channel;
|
||||
channel.set_id(channelId.toStdString());
|
||||
channel.set_display_name(query3->value(1).toString().toStdString());
|
||||
channel.set_access_level(roomChannelAccessFromKey(query3->value(2).toString()));
|
||||
channels.append(channel);
|
||||
}
|
||||
|
||||
addRoom(new Server_Room(query->value(0).toInt(), query->value(7).toInt(), query->value(1).toString(),
|
||||
query->value(2).toString(), query->value(3).toString().toLower(),
|
||||
query->value(4).toString().toLower(), static_cast<bool>(query->value(5).toInt()),
|
||||
query->value(6).toString(), gameTypes, this));
|
||||
query->value(6).toString(), gameTypes, this, channels));
|
||||
}
|
||||
} else {
|
||||
int size = settingsCache->beginReadArray("rooms/roomlist");
|
||||
|
|
@ -376,12 +437,38 @@ bool Servatrice::initServer()
|
|||
gameTypes.append(settingsCache->value("name").toString());
|
||||
}
|
||||
settingsCache->endArray();
|
||||
QList<ServerInfo_RoomChannel> channels;
|
||||
QSet<QString> seenChannelIds;
|
||||
int size3 = settingsCache->beginReadArray("channels");
|
||||
for (int k = 0; k < size3; ++k) {
|
||||
settingsCache->setArrayIndex(k);
|
||||
const QString channelId = settingsCache->value("id").toString();
|
||||
if (channelId.isEmpty()) {
|
||||
qWarning() << "Skipping room channel with empty id";
|
||||
continue;
|
||||
}
|
||||
if (Server_Room::isMainChannel(channelId)) {
|
||||
qWarning() << "Skipping room channel with reserved id" << channelId;
|
||||
continue;
|
||||
}
|
||||
if (seenChannelIds.contains(channelId)) {
|
||||
qWarning() << "Skipping duplicate room channel id" << channelId;
|
||||
continue;
|
||||
}
|
||||
seenChannelIds.insert(channelId);
|
||||
ServerInfo_RoomChannel channel;
|
||||
channel.set_id(channelId.toStdString());
|
||||
channel.set_display_name(settingsCache->value("displayname").toString().toStdString());
|
||||
channel.set_access_level(roomChannelAccessFromKey(settingsCache->value("access").toString()));
|
||||
channels.append(channel);
|
||||
}
|
||||
settingsCache->endArray();
|
||||
Server_Room *newRoom = new Server_Room(
|
||||
i, settingsCache->value("chathistorysize").toInt(), settingsCache->value("name").toString(),
|
||||
settingsCache->value("description").toString(),
|
||||
settingsCache->value("permissionlevel").toString().toLower(),
|
||||
settingsCache->value("privilegelevel").toString().toLower(), settingsCache->value("autojoin").toBool(),
|
||||
settingsCache->value("joinmessage").toString(), gameTypes, this);
|
||||
settingsCache->value("joinmessage").toString(), gameTypes, this, channels);
|
||||
addRoom(newRoom);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
class QSqlQuery;
|
||||
|
||||
#define DATABASE_SCHEMA_VERSION 37
|
||||
#define DATABASE_SCHEMA_VERSION 38
|
||||
|
||||
class Servatrice;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue