- Füge Debug-Ausgaben hinzu, um den Ablauf beim Hinzufügen neuer Benutzer zu protokollieren. - Sende wichtige Nachrichten (Raum- und Benutzerlisten) an den neuen Benutzer, bevor der checkerTask-Thread gestartet wird, um sicherzustellen, dass alle relevanten Informationen rechtzeitig bereitgestellt werden. - Implementiere eine kurze Verzögerung, um den Abschluss des WebSocket-Handshakes zu gewährleisten, bevor Nachrichten gesendet werden.
681 lines
22 KiB
C++
681 lines
22 KiB
C++
// entfernt: #include "user.h"
|
|
|
|
#include "chat_user.h"
|
|
#include "server.h"
|
|
#include "lib/tools.h"
|
|
#include "lib/base.h"
|
|
#include <json/json.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
#include "chat_room.h"
|
|
#include <iostream>
|
|
#include <netinet/in.h>
|
|
#include <netinet/tcp.h>
|
|
#include <sstream>
|
|
#include <cctype>
|
|
|
|
namespace Yc
|
|
{
|
|
namespace Lib
|
|
{
|
|
|
|
static bool _isValidHexColor(const std::string &c) {
|
|
if (c.size() != 7 || c[0] != '#') return false;
|
|
for (size_t i = 1; i < c.size(); ++i) {
|
|
char ch = c[i];
|
|
if (!std::isxdigit(static_cast<unsigned char>(ch))) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ChatUser::ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, int socket)
|
|
: _parent(std::move(parent)),
|
|
_name(name),
|
|
_color(color),
|
|
_socket(socket),
|
|
_stop(false)
|
|
{
|
|
// Hole DB-Connection
|
|
if (!_parent) {
|
|
// Fallback für SSL-Server ohne Parent
|
|
_user = Yc::Object::User(Json::Value());
|
|
_token = Yc::Lib::Tools::generateRandomString(32);
|
|
sendMsg(token, _token, _name, _color);
|
|
return;
|
|
}
|
|
auto server = _parent->getServer();
|
|
if (!server) {
|
|
// Fallback wenn Server nicht verfügbar
|
|
_user = Yc::Object::User(Json::Value());
|
|
_token = Yc::Lib::Tools::generateRandomString(32);
|
|
sendMsg(token, _token, _name, _color);
|
|
return;
|
|
}
|
|
auto db = server->_database;
|
|
// Suche Community-User
|
|
std::string query = "SELECT * FROM community.\"user\" WHERE username = '" + name + "' LIMIT 1;";
|
|
auto result = db->exec(query);
|
|
Json::Value userJson;
|
|
if (result.empty()) {
|
|
// Kein Community-User, lege Dummy an
|
|
userJson["display_name"] = name;
|
|
userJson["color"] = "#000000";
|
|
} else {
|
|
const auto& row = result[0];
|
|
int falukant_user_id = row["id"].as<int>();
|
|
// Suche Chat-User
|
|
std::string chatUserQuery = "SELECT * FROM chat.\"user\" WHERE falukant_user_id = " + std::to_string(falukant_user_id) + " LIMIT 1;";
|
|
auto chatUserResult = db->exec(chatUserQuery);
|
|
if (chatUserResult.empty()) {
|
|
// Chat-User anlegen
|
|
std::string insert = "INSERT INTO chat.\"user\" (falukant_user_id, display_name, color, show_gender, show_age, created_at, updated_at) VALUES (" +
|
|
std::to_string(falukant_user_id) + ", '" + name + "', '#000000', true, true, NOW(), NOW()) RETURNING *;";
|
|
auto newUser = db->exec(insert);
|
|
if (!newUser.empty()) {
|
|
const auto& u = newUser[0];
|
|
userJson["id"] = u["id"].as<int>();
|
|
userJson["falukant_user_id"] = u["falukant_user_id"].as<int>();
|
|
userJson["display_name"] = u["display_name"].c_str();
|
|
userJson["color"] = u["color"].c_str();
|
|
userJson["show_gender"] = u["show_gender"].as<bool>();
|
|
userJson["show_age"] = u["show_age"].as<bool>();
|
|
userJson["created_at"] = u["created_at"].c_str();
|
|
userJson["updated_at"] = u["updated_at"].c_str();
|
|
}
|
|
} else {
|
|
const auto& u = chatUserResult[0];
|
|
userJson["id"] = u["id"].as<int>();
|
|
userJson["falukant_user_id"] = u["falukant_user_id"].as<int>();
|
|
userJson["display_name"] = u["display_name"].c_str();
|
|
userJson["color"] = u["color"].c_str();
|
|
userJson["show_gender"] = u["show_gender"].as<bool>();
|
|
userJson["show_age"] = u["show_age"].as<bool>();
|
|
userJson["created_at"] = u["created_at"].c_str();
|
|
userJson["updated_at"] = u["updated_at"].c_str();
|
|
}
|
|
// Rechte laden
|
|
std::string rightsQuery = "SELECT r.tr FROM chat.user_rights ur JOIN chat.rights r ON ur.chat_right_id = r.id WHERE ur.chat_user_id = " + std::to_string(userJson["id"].asInt()) + ";";
|
|
auto rightsResult = db->exec(rightsQuery);
|
|
Json::Value rights(Json::arrayValue);
|
|
for (const auto& r : rightsResult) {
|
|
rights.append(r["tr"].c_str());
|
|
}
|
|
userJson["rights"] = rights;
|
|
}
|
|
_user = Yc::Object::User(userJson);
|
|
// Prefer DB color if available
|
|
if (_user.id() != 0 && !_user.color().empty()) {
|
|
_color = _user.color();
|
|
}
|
|
_token = Yc::Lib::Tools::generateRandomString(32);
|
|
// Beim Initial-Token direkt Name und aktuelle Farbe mitsenden, damit der Client "ich" korrekt färben kann
|
|
sendMsg(token, _token, _name, _color);
|
|
// Thread-Start erfolgt jetzt explizit per start(), nicht im Konstruktor
|
|
}
|
|
|
|
ChatUser::ChatUser(std::shared_ptr<ChatRoom> parent, std::string name, std::string color, int socket, std::shared_ptr<Database> database)
|
|
: _parent(std::move(parent)),
|
|
_name(name),
|
|
_color(color),
|
|
_socket(socket),
|
|
_stop(false)
|
|
{
|
|
// Verwende die direkt übergebene Datenbank
|
|
if (!database) {
|
|
// Fallback wenn keine Datenbank verfügbar
|
|
_user = Yc::Object::User(Json::Value());
|
|
_token = Yc::Lib::Tools::generateRandomString(32);
|
|
sendMsg(token, _token, _name, _color);
|
|
return;
|
|
}
|
|
auto db = database;
|
|
// Suche Community-User
|
|
std::string query = "SELECT * FROM community.\"user\" WHERE username = '" + name + "' LIMIT 1;";
|
|
auto result = db->exec(query);
|
|
Json::Value userJson;
|
|
if (result.empty()) {
|
|
// Kein Community-User, lege Dummy an
|
|
userJson["display_name"] = name;
|
|
userJson["color"] = "#000000";
|
|
} else {
|
|
const auto& row = result[0];
|
|
int falukant_user_id = row["id"].as<int>();
|
|
// Suche Chat-User
|
|
std::string chatUserQuery = "SELECT * FROM chat.\"user\" WHERE falukant_user_id = " + std::to_string(falukant_user_id) + " LIMIT 1;";
|
|
auto chatUserResult = db->exec(chatUserQuery);
|
|
if (chatUserResult.empty()) {
|
|
// Chat-User anlegen
|
|
std::string insert = "INSERT INTO chat.\"user\" (falukant_user_id, display_name, color, show_gender, show_age, created_at, updated_at) VALUES (" +
|
|
std::to_string(falukant_user_id) + ", '" + name + "', '#000000', true, true, NOW(), NOW()) RETURNING *;";
|
|
auto newUser = db->exec(insert);
|
|
if (!newUser.empty()) {
|
|
const auto& u = newUser[0];
|
|
userJson["id"] = u["id"].as<int>();
|
|
userJson["falukant_user_id"] = u["falukant_user_id"].as<int>();
|
|
userJson["display_name"] = u["display_name"].c_str();
|
|
userJson["color"] = u["color"].c_str();
|
|
userJson["show_gender"] = u["show_gender"].as<bool>();
|
|
userJson["show_age"] = u["show_age"].as<bool>();
|
|
userJson["created_at"] = u["created_at"].c_str();
|
|
userJson["updated_at"] = u["updated_at"].c_str();
|
|
}
|
|
} else {
|
|
const auto& u = chatUserResult[0];
|
|
userJson["id"] = u["id"].as<int>();
|
|
userJson["falukant_user_id"] = u["falukant_user_id"].as<int>();
|
|
userJson["display_name"] = u["display_name"].c_str();
|
|
userJson["color"] = u["color"].c_str();
|
|
userJson["show_gender"] = u["show_gender"].as<bool>();
|
|
userJson["show_age"] = u["show_age"].as<bool>();
|
|
userJson["created_at"] = u["created_at"].c_str();
|
|
userJson["updated_at"] = u["updated_at"].c_str();
|
|
}
|
|
// Rechte laden
|
|
std::string rightsQuery = "SELECT r.tr FROM chat.user_rights ur JOIN chat.rights r ON ur.chat_right_id = r.id WHERE ur.chat_user_id = " + std::to_string(userJson["id"].asInt()) + ";";
|
|
auto rightsResult = db->exec(rightsQuery);
|
|
Json::Value rights(Json::arrayValue);
|
|
for (const auto& r : rightsResult) {
|
|
rights.append(r["tr"].c_str());
|
|
}
|
|
userJson["rights"] = rights;
|
|
}
|
|
_user = Yc::Object::User(userJson);
|
|
// Prefer DB color if available
|
|
if (_user.id() != 0 && !_user.color().empty()) {
|
|
_color = _user.color();
|
|
}
|
|
_token = Yc::Lib::Tools::generateRandomString(32);
|
|
// Beim Initial-Token direkt Name und aktuelle Farbe mitsenden, damit der Client "ich" korrekt färben kann
|
|
sendMsg(token, _token, _name, _color);
|
|
// Thread-Start erfolgt jetzt explizit per start(), nicht im Konstruktor
|
|
}
|
|
|
|
ChatUser::~ChatUser()
|
|
{
|
|
// Hinweis: Thread wird nicht im Destruktor gejoint, um Deadlocks zu vermeiden!
|
|
// Der Thread muss explizit von außen gestoppt und gejoint werden (z.B. im ChatRoom beim Entfernen des Users).
|
|
}
|
|
|
|
std::string ChatUser::name() const
|
|
{
|
|
return _name;
|
|
}
|
|
|
|
std::string ChatUser::getToken() const
|
|
{
|
|
return _token;
|
|
}
|
|
|
|
bool ChatUser::validateToken(std::string token)
|
|
{
|
|
return (token == _token);
|
|
}
|
|
|
|
bool ChatUser::isUser(std::shared_ptr<ChatUser> toValidate)
|
|
{
|
|
return (toValidate.get() == this);
|
|
}
|
|
|
|
void ChatUser::sendMsg(MsgType type, const char *message, std::string userName, std::string color)
|
|
{
|
|
sendMsg(type, std::string(message), userName, color);
|
|
}
|
|
|
|
void ChatUser::sendMsg(MsgType type, std::string message, std::string userName, std::string color)
|
|
{
|
|
// Standardwerte für leere Felder setzen
|
|
if (userName.empty()) userName = _name;
|
|
if (color.empty()) color = _color;
|
|
Json::Value sendMessage;
|
|
sendMessage["type"] = type;
|
|
sendMessage["message"] = message;
|
|
sendMessage["userName"] = userName;
|
|
sendMessage["color"] = color;
|
|
send(sendMessage);
|
|
}
|
|
|
|
void ChatUser::sendMsg(MsgType type, Json::Value message, std::string userName, std::string color)
|
|
{
|
|
// Standardwerte für leere Felder setzen
|
|
if (userName.empty()) userName = _name;
|
|
if (color.empty()) color = _color;
|
|
Json::Value sendMessage;
|
|
sendMessage["type"] = type;
|
|
sendMessage["message"] = message;
|
|
sendMessage["userName"] = userName;
|
|
sendMessage["color"] = color;
|
|
send(sendMessage);
|
|
}
|
|
|
|
void ChatUser::checkerTask()
|
|
{
|
|
try {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] checkerTask started for user: " << _name << " (socket: " << _socket << ")" << std::endl;
|
|
#endif
|
|
|
|
// Warte kurz, damit der WebSocket-Handshake vollständig abgeschlossen ist
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] checkerTask: Waiting 500ms for WebSocket handshake..." << std::endl;
|
|
#endif
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] checkerTask: Starting heartbeat monitoring for user: " << _name << std::endl;
|
|
#endif
|
|
|
|
// Heartbeat-Intervall: Alle 10 Sekunden Verbindung prüfen
|
|
const int HEARTBEAT_INTERVAL = 2;
|
|
int heartbeatCounter = 0;
|
|
|
|
while (!_stop)
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
|
|
// Heartbeat-Check alle 10 Sekunden
|
|
heartbeatCounter++;
|
|
if (heartbeatCounter >= HEARTBEAT_INTERVAL) {
|
|
heartbeatCounter = 0;
|
|
|
|
// Prüfe Verbindung mit MSG_PEEK (nicht-blockierend)
|
|
char peek;
|
|
ssize_t r = recv(_socket, &peek, 1, MSG_PEEK | MSG_DONTWAIT);
|
|
|
|
if (r == 0) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Verbindung zum Client abgebrochen (Token: <hidden>)" << std::endl;
|
|
#endif
|
|
_parent->removeUserDisconnected(shared_from_this());
|
|
_stop = true;
|
|
if (thread.joinable() && std::this_thread::get_id() == thread.get_id()) {
|
|
thread.detach();
|
|
}
|
|
return;
|
|
} else if (r < 0) {
|
|
// EINTR = Interrupted system call (normal), EAGAIN/EWOULDBLOCK = No data available (normal)
|
|
// Andere Fehler bedeuten Verbindungsabbruch
|
|
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Socket-Fehler: " << strerror(errno) << " (Token: <hidden>)" << std::endl;
|
|
#endif
|
|
_parent->removeUserDisconnected(shared_from_this());
|
|
_stop = true;
|
|
if (thread.joinable() && std::this_thread::get_id() == thread.get_id()) {
|
|
thread.detach();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Optional: Sende Heartbeat-Ping an Client
|
|
// (kann helfen, NAT/Firewall-Verbindungen aktiv zu halten)
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Heartbeat check passed for user: " << _name << std::endl;
|
|
#endif
|
|
}
|
|
|
|
// Ursprüngliche Verbindungsprüfung (alle 1 Sekunde)
|
|
{
|
|
char peek;
|
|
ssize_t r = recv(_socket, &peek, 1, MSG_PEEK);
|
|
if (r == 0) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Verbindung zum Client abgebrochen (Token: <hidden>)" << std::endl;
|
|
#endif
|
|
_parent->removeUserDisconnected(shared_from_this());
|
|
_stop = true;
|
|
if (thread.joinable() && std::this_thread::get_id() == thread.get_id()) {
|
|
thread.detach();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::string msg = readSocket(_socket);
|
|
if (msg == "")
|
|
{
|
|
// Kein Inhalt gelesen; kann Idle/Keepalive sein
|
|
continue;
|
|
}
|
|
handleMessage(msg);
|
|
}
|
|
} catch (const std::exception& ex) {
|
|
// swallow, optional hook
|
|
} catch (...) {
|
|
// swallow, optional hook
|
|
}
|
|
}
|
|
|
|
void ChatUser::stop()
|
|
{
|
|
_stop = true;
|
|
// Socket als ungültig markieren
|
|
_socket = -1;
|
|
}
|
|
|
|
std::string ChatUser::color() const
|
|
{
|
|
return _color;
|
|
}
|
|
|
|
void ChatUser::setParent(std::shared_ptr<ChatRoom> parent)
|
|
{
|
|
_parent = std::move(parent);
|
|
}
|
|
|
|
void ChatUser::send(std::string out) {
|
|
// Prüfe ob Socket noch gültig ist
|
|
if (_socket < 0 || _stop) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Skipping send - socket invalid (" << _socket << ") or user stopped (" << _stop << ") for user: " << _name << std::endl;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Sending message to user: " << _name << " (socket: " << _socket << ")" << std::endl;
|
|
#endif
|
|
|
|
// Entferne ggf. Token-Felder aus JSON-Strings und sende über Socket/WebSocket
|
|
Base::sanitizeTokensInString(out);
|
|
Base::send(_socket, out);
|
|
}
|
|
|
|
void ChatUser::send(Json::Value out) {
|
|
// Prüfe ob Socket noch gültig ist
|
|
if (_socket < 0 || _stop) {
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Skipping send - socket invalid (" << _socket << ") or user stopped (" << _stop << ") for user: " << _name << std::endl;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Sending JSON message to user: " << _name << " (socket: " << _socket << ")" << std::endl;
|
|
#endif
|
|
|
|
// Entferne rekursiv alle Token-Felder und sende über Socket/WebSocket
|
|
Base::sanitizeTokens(out);
|
|
Base::send(_socket, out);
|
|
}
|
|
|
|
void ChatUser::handleMessage(std::string message)
|
|
{
|
|
Json::Value jsonTree = getJsonTree(message);
|
|
// Eingehende Nachricht (nur im Debug-Mode)
|
|
#ifdef YC_DEBUG
|
|
{
|
|
Json::Value toLog = jsonTree;
|
|
if (toLog.isMember("token")) toLog["token"] = "<hidden>";
|
|
std::cout << "[Debug] <- Client(fd=" << _socket << ", user='" << _name << "'): " << getJsonString(toLog) << std::endl;
|
|
}
|
|
#endif
|
|
if (jsonTree["token"].asString() != _token)
|
|
{
|
|
return;
|
|
}
|
|
if (jsonTree["type"].asString() == "message")
|
|
{
|
|
checkString(jsonTree["message"].asString());
|
|
}
|
|
else if (jsonTree["type"].asString() == "dice")
|
|
{
|
|
// Neue Würfel-Behandlung für das Würfelspiel
|
|
if (jsonTree.isMember("value")) {
|
|
int diceValue = jsonTree["value"].asInt();
|
|
if (!_parent->rollDice(shared_from_this(), diceValue)) {
|
|
sendMsg(ChatUser::error, "dice_roll_failed", "", "");
|
|
}
|
|
} else {
|
|
// Fallback: Einfacher Würfelwurf
|
|
doDice();
|
|
}
|
|
}
|
|
else if (jsonTree["type"].asString() == "start_dice_game")
|
|
{
|
|
// Würfelspiel starten (nur Admin)
|
|
if (jsonTree.isMember("rounds")) {
|
|
int rounds = jsonTree["rounds"].asInt();
|
|
if (rounds < 1 || rounds > 10) {
|
|
sendMsg(ChatUser::error, "invalid_rounds", "", "");
|
|
return;
|
|
}
|
|
|
|
if (!_parent->startDiceGame(rounds, shared_from_this())) {
|
|
sendMsg(ChatUser::error, "dice_game_start_failed", "", "");
|
|
}
|
|
} else {
|
|
sendMsg(ChatUser::error, "missing_rounds", "", "");
|
|
}
|
|
}
|
|
else if (jsonTree["type"].asString() == "end_dice_game")
|
|
{
|
|
// Würfelspiel beenden (nur Admin)
|
|
_parent->endDiceGame();
|
|
}
|
|
else if (jsonTree["type"].asString() == "scream")
|
|
{
|
|
_parent->addMessage(ChatUser::scream, jsonTree["message"].asString(), std::string(_name), std::string(_color));
|
|
}
|
|
else if (jsonTree["type"].asString() == "do")
|
|
{
|
|
// "do" erwartet "value" (Aktion) und optional "to" (Ziel-User)
|
|
std::string action = jsonTree.isMember("value") ? jsonTree["value"].asString() : "";
|
|
std::string targetUser = jsonTree.isMember("to") ? jsonTree["to"].asString() : "";
|
|
|
|
if (action.empty()) {
|
|
sendMsg(ChatUser::error, "missing_action", "", "");
|
|
return;
|
|
}
|
|
|
|
// Erstelle strukturierte Nachricht mit Ziel-User und Aktion
|
|
Json::Value doMsg = Json::objectValue;
|
|
doMsg["tr"] = "user_action";
|
|
doMsg["action"] = action;
|
|
if (!targetUser.empty()) {
|
|
doMsg["to"] = targetUser;
|
|
|
|
// Suche den Ziel-User und füge dessen Informationen hinzu
|
|
auto targetUserObj = _parent->findUserByName(targetUser);
|
|
if (targetUserObj) {
|
|
doMsg["targetName"] = targetUserObj->name();
|
|
doMsg["targetColor"] = targetUserObj->color();
|
|
}
|
|
}
|
|
|
|
// Debug-Ausgabe für "do"-Nachrichten
|
|
#ifdef YC_DEBUG
|
|
std::cout << "[Debug] Sending do message: type=" << ChatUser::dosomething << ", action=" << action << ", to=" << (targetUser.empty() ? "all" : targetUser) << std::endl;
|
|
#endif
|
|
|
|
_parent->addMessage(ChatUser::dosomething, doMsg, std::string(_name), std::string(_color));
|
|
}
|
|
else if (jsonTree["type"].asString() == "color")
|
|
{
|
|
std::string newColor = jsonTree.isMember("value") ? jsonTree["value"].asString() : "";
|
|
std::string oldColor = _color;
|
|
if (!_isValidHexColor(newColor)) {
|
|
sendMsg(ChatUser::error, "invalid_color", "", "");
|
|
return;
|
|
}
|
|
_color = newColor;
|
|
_user.set_color(newColor);
|
|
// Persistieren, falls DB-ID vorhanden
|
|
try {
|
|
if (_user.id() != 0) {
|
|
auto server = _parent->getServer();
|
|
auto db = server->_database;
|
|
std::string query = "UPDATE chat.\"user\" SET color = '" + newColor + "', updated_at = NOW() WHERE id = " + std::to_string(_user.id()) + ";";
|
|
(void)db->exec(query);
|
|
}
|
|
} catch (...) {
|
|
// Ignoriere DB-Fehler still
|
|
}
|
|
// Bestätigung an User
|
|
sendMsg(ChatUser::system, "color_changed", "", newColor);
|
|
// Broadcast an andere: alte und neue Farbe mitsenden
|
|
{
|
|
Json::Value msg = Json::objectValue;
|
|
msg["tr"] = "user_color_changed";
|
|
msg["from"] = oldColor;
|
|
msg["to"] = newColor;
|
|
_parent->addMessage(ChatUser::system, msg, _name, newColor);
|
|
}
|
|
}
|
|
else if (jsonTree["type"].asString() == "join")
|
|
{
|
|
changeRoom(jsonTree["room"].asString(), jsonTree["password"].asString());
|
|
}
|
|
else if (jsonTree["type"].asString() == "userlist")
|
|
{
|
|
sendUserList();
|
|
}
|
|
}
|
|
|
|
void ChatUser::checkString(std::string message)
|
|
{
|
|
if (message.substr(0, 6) == "/join ")
|
|
{
|
|
message = message.substr(6);
|
|
if (message.find(" ") == std::string::npos)
|
|
{
|
|
changeRoom(message, "");
|
|
}
|
|
else
|
|
{
|
|
std::string room = message.substr(0, message.find(" "));
|
|
std::string password = message.substr(message.find(" ") + 1);
|
|
changeRoom(room, password);
|
|
}
|
|
}
|
|
else if (message.substr(0, 7) == "/color ")
|
|
{
|
|
std::string newColor = message.substr(7);
|
|
std::string oldColor = _color;
|
|
if (!_isValidHexColor(newColor)) {
|
|
sendMsg(ChatUser::error, "invalid_color", "", "");
|
|
return;
|
|
}
|
|
_color = newColor;
|
|
_user.set_color(newColor);
|
|
try {
|
|
if (_user.id() != 0) {
|
|
auto server = _parent->getServer();
|
|
auto db = server->_database;
|
|
std::string query = "UPDATE chat.\"user\" SET color = '" + newColor + "', updated_at = NOW() WHERE id = " + std::to_string(_user.id()) + ";";
|
|
(void)db->exec(query);
|
|
}
|
|
} catch (...) {
|
|
// ignore
|
|
}
|
|
sendMsg(ChatUser::system, "color_changed", "", newColor);
|
|
{
|
|
Json::Value msg = Json::objectValue;
|
|
msg["tr"] = "user_color_changed";
|
|
msg["from"] = oldColor;
|
|
msg["to"] = newColor;
|
|
_parent->addMessage(ChatUser::system, msg, _name, newColor);
|
|
}
|
|
}
|
|
else if (message == "/dice")
|
|
{
|
|
doDice();
|
|
}
|
|
else if (message.substr(0, 15) == "/start_dice_game")
|
|
{
|
|
// Format: /start_dice_game <runden>
|
|
if (message.length() > 16) {
|
|
std::string roundsStr = message.substr(16);
|
|
try {
|
|
int rounds = std::stoi(roundsStr);
|
|
if (rounds >= 1 && rounds <= 10) {
|
|
if (!_parent->startDiceGame(rounds, shared_from_this())) {
|
|
sendMsg(ChatUser::error, "dice_game_start_failed", "", "");
|
|
}
|
|
} else {
|
|
sendMsg(ChatUser::error, "invalid_rounds", "", "");
|
|
}
|
|
} catch (...) {
|
|
sendMsg(ChatUser::error, "invalid_rounds", "", "");
|
|
}
|
|
} else {
|
|
sendMsg(ChatUser::error, "missing_rounds", "", "");
|
|
}
|
|
}
|
|
else if (message == "/end_dice_game")
|
|
{
|
|
_parent->endDiceGame();
|
|
}
|
|
else if (message == "/reload_rooms")
|
|
{
|
|
// Raumliste neu laden
|
|
_parent->reloadRoomList();
|
|
}
|
|
else if (message.substr(0, 5) == "/roll ")
|
|
{
|
|
// Format: /roll <würfelwert>
|
|
if (message.length() > 6) {
|
|
std::string diceStr = message.substr(6);
|
|
try {
|
|
int diceValue = std::stoi(diceStr);
|
|
if (diceValue >= 1 && diceValue <= 6) {
|
|
if (!_parent->rollDice(shared_from_this(), diceValue)) {
|
|
sendMsg(ChatUser::error, "dice_roll_failed", "", "");
|
|
}
|
|
} else {
|
|
sendMsg(ChatUser::error, "invalid_dice_value", "", "");
|
|
}
|
|
} catch (...) {
|
|
sendMsg(ChatUser::error, "invalid_dice_value", "", "");
|
|
}
|
|
} else {
|
|
sendMsg(ChatUser::error, "missing_dice_value", "", "");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_parent->addMessage(ChatUser::message, std::string(message), std::string(_name), std::string(_color));
|
|
}
|
|
}
|
|
|
|
void ChatUser::sendUserList()
|
|
{
|
|
Json::Value userList = _parent->userList();
|
|
Json::Value msg = Json::objectValue;
|
|
msg["userlist"] = userList;
|
|
sendMsg(userListe, msg, "", "");
|
|
}
|
|
|
|
void ChatUser::doDice()
|
|
{
|
|
switch (_parent->addDice(shared_from_this(), (rand() % 6) + 1))
|
|
{
|
|
case 1:
|
|
sendMsg(system, "dice_not_possible", "", "");
|
|
break;
|
|
case 2:
|
|
sendMsg(system, "dice_allready_done", "", "");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ChatUser::changeRoom(std::string newRoom, std::string password)
|
|
{
|
|
if (!_parent->userToNewRoom(shared_from_this(), newRoom, password))
|
|
{
|
|
sendMsg(ChatUser::system, "room_not_possible", "", "");
|
|
}
|
|
}
|
|
|
|
void ChatUser::start() {
|
|
auto self = shared_from_this();
|
|
thread = std::thread([self]() { self->checkerTask(); });
|
|
}
|
|
|
|
} // namespace Lib
|
|
} // namespace Yc
|