From 6f7b994684032060f0a2d7af82f4f3572293b8d6 Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Thu, 12 Jun 2025 21:41:41 +0800 Subject: internal!: organized internal structure, using cmake now --- src/command/commandHandler.cpp | 101 +++++++++++++++++++++++++++++++++++++++++ src/command/commandHandler.hpp | 71 +++++++++++++++++++++++++++++ src/command/commands.cpp | 95 ++++++++++++++++++++++++++++++++++++++ src/command/commands.hpp | 1 + 4 files changed, 268 insertions(+) create mode 100644 src/command/commandHandler.cpp create mode 100644 src/command/commandHandler.hpp create mode 100644 src/command/commands.cpp create mode 100644 src/command/commands.hpp (limited to 'src/command') diff --git a/src/command/commandHandler.cpp b/src/command/commandHandler.cpp new file mode 100644 index 0000000..4aa6723 --- /dev/null +++ b/src/command/commandHandler.cpp @@ -0,0 +1,101 @@ +#include "commandHandler.hpp" +#include "../debug/log.hpp" +#include "../core/session.hpp" +#include +#include + +CCommandHandler::CCommandHandler() { + log(LOG, "CommandHandler: initialized"); +} + +CCommandHandler::~CCommandHandler() { + log(SYS, "CommandHandler: bye"); + m_vCommands.clear(); +} + +bool CCommandHandler::isCommand(const std::string &str) const { + return !str.empty() && str.front() == '/'; +}; + +bool CCommandHandler::validCommand(const std::string &command) const { + return std::ranges::any_of(m_vCommands, [&command](const std::shared_ptr &cmd) { + return cmd->name == command; + }); +} + +bool CCommandHandler::registerCommand(const SCommand &command) { + if (command.name.empty() || !command.exe || !command.parser) + return false; + + m_vCommands.push_back(std::make_shared(command)); + + log(TRACE, "CommandHandler: registered command: {}", command.name); + + return true; +} + +std::shared_ptr CCommandHandler::getCommand(const std::string &command) const { + auto it = std::ranges::find_if(m_vCommands, [&command](const std::shared_ptr &cmd) { return cmd->name == command; }); + + if (it != m_vCommands.end()) + return *it; + + return nullptr; +} + +CCommandHandler::SResult CCommandHandler::exeCommand(const std::shared_ptr command, const std::string &args) const { + const auto [parsed, good] = command->parser(args); + if (!good) + try { + return {.result = std::any_cast(parsed), .good = false}; + } catch (const std::bad_any_cast &) { + return {.result = "", .good = false}; + } + + log(TRACE, "CommandHandler: executing command: {}", command->name); + return command->exe(parsed); +} + +CCommandHandler::SResult CCommandHandler::newCommand(const std::string &command, const std::string &args, std::shared_ptr session) const { + if (!validCommand(command)) + return {.result = "Invalid command", .good = false}; + + auto cmd = getCommand(command); + if (!cmd) + return {.result = "Command not found", .good = false}; + + if (cmd->requireAdmin && !session->isAdmin()) + return {.result = "Permission denied", .good = false}; + + return exeCommand(cmd, args); +} + +void CCommandHandler::handleCommand(std::string input, std::shared_ptr user) const { + input = input.substr(1); + + std::string command; + std::string args; + + size_t spacePos = input.find(' '); + if (spacePos == std::string::npos) { + command = input; + args = ""; + } else { + command = input.substr(0, spacePos); + // skip extra spaces + size_t startPos = input.find_first_not_of(' ', spacePos); + if (startPos != std::string::npos) + args = input.substr(startPos); + } + + args.empty() ? log(LOG, "Spawning Command: {}", command) : log(LOG, "Spawning Command: {}, Args: {}", command, args); + const auto exe = newCommand(command, args, user); + + std::function outputter = [](eFormatType type, const std::string &msg) { log(type, "{}", msg); }; + + if (!user->getIp().empty()) + outputter = [user](eFormatType, const std::string &msg) { user->write(msg); }; + + exe.good ? exe.result.empty() ? outputter(LOG, "Command succeeded") : outputter(LOG, exe.result) : exe.result.empty() ? outputter(ERR, "Command failed") + : outputter(ERR, exe.result); +}; diff --git a/src/command/commandHandler.hpp b/src/command/commandHandler.hpp new file mode 100644 index 0000000..7fc20d5 --- /dev/null +++ b/src/command/commandHandler.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "../core/session.hpp" +#include +#include +#include +#include +#include +#include + +class CCommandHandler { + public: + CCommandHandler(); + ~CCommandHandler(); + + struct SResult { + std::string result; + bool good = false; + }; + + struct SParseResult { + std::any data; + bool good = false; + }; + + struct SCommand { + bool requireAdmin = true; + std::string name; + std::function parser; + std::function exe; + }; + + bool isCommand(const std::string &str) const; + bool validCommand(const std::string &command) const; + // return false if command struct is invalid + bool registerCommand(const SCommand &command); + SResult newCommand(const std::string &command, const std::string &args, std::shared_ptr session) const; + void handleCommand(std::string input, std::shared_ptr user = nullptr) const; + + template + struct SMakeCommandArgs { + std::string name; + std::function(const std::string &args)> parser; + std::function exe; + }; + + template + static SCommand makeCommand(SMakeCommandArgs args) { + return { + .name = std::move(args.name), + .parser = [parser = std::move(args.parser)](const std::string &args) -> SParseResult { + auto [data, good] = parser(args); + return {.data = std::any(std::move(data)), .good = good}; + }, + .exe = [exe = std::move(args.exe)](const std::any &parsed) -> SResult { + try { + return exe(std::any_cast(parsed)); + } catch (const std::bad_any_cast &) { + return {.result = "Invalid data type", .good = false}; + } + } + }; + } + + private: + std::shared_ptr getCommand(const std::string &command) const; + SResult exeCommand(const std::shared_ptr command, const std::string &args) const; + + std::vector> m_vCommands; +}; +inline std::unique_ptr g_pCommandHandler; diff --git a/src/command/commands.cpp b/src/command/commands.cpp new file mode 100644 index 0000000..6f3db68 --- /dev/null +++ b/src/command/commands.cpp @@ -0,0 +1,95 @@ +#include "commands.hpp" +#include "commandHandler.hpp" +#include "../debug/format.hpp" +#include "../debug/log.hpp" +#include "../core/sessionManager.hpp" + +#define NOREASON "No reason specified"; + +struct SKickArgs { + std::string target; + std::optional reason; +}; + +struct SMuteArgs { + std::string target; + std::optional reason; +}; + +void registerCommands() { + g_pCommandHandler->registerCommand({.name = "raw", .parser = [](const std::string &args) -> CCommandHandler::SParseResult { return {.data = args, .good = true}; }, .exe = [](const std::any &parsed) -> CCommandHandler::SResult { + const std::string &data = std::any_cast(parsed); + g_pSessionManager->broadcast(data); + return {.result = "Broadcasted: " + data, .good = true}; }}); + + g_pCommandHandler->registerCommand(CCommandHandler::makeCommand({.name = "kick", .parser = [](const std::string &args) -> std::pair { + size_t pos = args.find(' '); + std::string target = args; + std::optional reason = std::nullopt; + + if (pos != std::string::npos) { + target = args.substr(0, pos); + reason = args.substr(pos + 1); + } + + if (target.empty()) + return {SKickArgs{}, false}; + + return { + SKickArgs{.target=target, .reason=reason}, true}; }, .exe = [](const SKickArgs &data) -> CCommandHandler::SResult { + const auto session = g_pSessionManager->getByName(data.target); + if (!session) + return {.result = "User '" + data.target + "' not found", .good = false}; + + const auto reason = data.reason ? *data.reason : NOREASON; + g_pSessionManager->kick(session->self, true, reason); + return {.result = "Kicked '" + data.target + "': " + reason, .good = true}; }})); + + g_pCommandHandler->registerCommand({.requireAdmin = false, .name = "list", .parser = [](const std::string &args) -> CCommandHandler::SParseResult { + (void)args; + return {.data = std::any(), .good = true}; }, .exe = [](const std::any &parsed) -> CCommandHandler::SResult { + (void)parsed; + std::string result = "ID \t Name \t IP \t Muted\n"; + for (const auto &session : g_pSessionManager->getSessions()) + result += NFormatter::fmt(NONEWLINE, "{} \t {} \t {} \t {}\n", (uintptr_t)session.get(), session->getName(), session->getIp(), (session->isMuted() ? "[MUTED]" : "[UNMUTED]")); + + + return {.result = result, .good = true}; }}); + + g_pCommandHandler->registerCommand(CCommandHandler::makeCommand({.name = "mute", .parser = [](const std::string &args) -> std::pair { + size_t pos = args.find(' '); + std::string target = args; + std::optional reason; + + if (pos != std::string::npos) { + target = args.substr(0, pos); + reason = args.substr(pos + 1); + } + + if (target.empty()) + return {SMuteArgs{}, false}; + + return {SMuteArgs{.target=target, .reason=reason}, true}; }, .exe = [](const SMuteArgs &data) -> CCommandHandler::SResult { + const auto session = g_pSessionManager->getByName(data.target); + if (!session) + return {.result = "User '" + data.target + "' not found", .good = false}; + + const auto reason = data.reason ? *data.reason : NOREASON; + + session->setMuted(true); + session->write("You have been muted for: {}", reason); + return {.result = "Muted '" + data.target + "': " + reason, .good = true}; }})); // TODO: use fmt + + g_pCommandHandler->registerCommand(CCommandHandler::makeCommand({.name = "unmute", .parser = [](const std::string &args) -> std::pair { + if (args.empty()) + return {SMuteArgs{}, false}; + + return {SMuteArgs{.target=args, .reason="Unmuted"}, true}; }, .exe = [](const SMuteArgs &data) -> CCommandHandler::SResult { + const auto session = g_pSessionManager->getByName(data.target); + if (!session) + return {.result = "User '" + data.target + "' not found", .good = false}; + + session->setMuted(false); + session->write("You have been unmuted"); + return {.result = "Unmuted '" + data.target + "'", .good = true}; }})); +} diff --git a/src/command/commands.hpp b/src/command/commands.hpp new file mode 100644 index 0000000..b49b6dc --- /dev/null +++ b/src/command/commands.hpp @@ -0,0 +1 @@ +void registerCommands(); -- cgit v1.2.3