summaryrefslogtreecommitdiff
path: root/src/command
diff options
context:
space:
mode:
Diffstat (limited to 'src/command')
-rw-r--r--src/command/commandHandler.cpp101
-rw-r--r--src/command/commandHandler.hpp71
-rw-r--r--src/command/commands.cpp95
-rw-r--r--src/command/commands.hpp1
4 files changed, 268 insertions, 0 deletions
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 <algorithm>
+#include <memory>
+
+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<SCommand> &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<SCommand>(command));
+
+ log(TRACE, "CommandHandler: registered command: {}", command.name);
+
+ return true;
+}
+
+std::shared_ptr<CCommandHandler::SCommand> CCommandHandler::getCommand(const std::string &command) const {
+ auto it = std::ranges::find_if(m_vCommands, [&command](const std::shared_ptr<SCommand> &cmd) { return cmd->name == command; });
+
+ if (it != m_vCommands.end())
+ return *it;
+
+ return nullptr;
+}
+
+CCommandHandler::SResult CCommandHandler::exeCommand(const std::shared_ptr<SCommand> command, const std::string &args) const {
+ const auto [parsed, good] = command->parser(args);
+ if (!good)
+ try {
+ return {.result = std::any_cast<std::string>(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<CSession> 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<CSession> 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<void(eFormatType, const std::string &)> 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 <any>
+#include <functional>
+#include <memory>
+#include <netinet/in.h>
+#include <string>
+#include <vector>
+
+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<SParseResult(const std::string &args)> parser;
+ std::function<SResult(const std::any &parsed)> 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<CSession> session) const;
+ void handleCommand(std::string input, std::shared_ptr<CSession> user = nullptr) const;
+
+ template <typename T>
+ struct SMakeCommandArgs {
+ std::string name;
+ std::function<std::pair<T, bool>(const std::string &args)> parser;
+ std::function<SResult(const T &parsed)> exe;
+ };
+
+ template <typename T>
+ static SCommand makeCommand(SMakeCommandArgs<T> 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<const T &>(parsed));
+ } catch (const std::bad_any_cast &) {
+ return {.result = "Invalid data type", .good = false};
+ }
+ }
+ };
+ }
+
+ private:
+ std::shared_ptr<CCommandHandler::SCommand> getCommand(const std::string &command) const;
+ SResult exeCommand(const std::shared_ptr<SCommand> command, const std::string &args) const;
+
+ std::vector<std::shared_ptr<SCommand>> m_vCommands;
+};
+inline std::unique_ptr<CCommandHandler> 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<std::string> reason;
+};
+
+struct SMuteArgs {
+ std::string target;
+ std::optional<std::string> 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<const std::string &>(parsed);
+ g_pSessionManager->broadcast(data);
+ return {.result = "Broadcasted: " + data, .good = true}; }});
+
+ g_pCommandHandler->registerCommand(CCommandHandler::makeCommand<SKickArgs>({.name = "kick", .parser = [](const std::string &args) -> std::pair<SKickArgs, bool> {
+ size_t pos = args.find(' ');
+ std::string target = args;
+ std::optional<std::string> 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<SMuteArgs>({.name = "mute", .parser = [](const std::string &args) -> std::pair<SMuteArgs, bool> {
+ size_t pos = args.find(' ');
+ std::string target = args;
+ std::optional<std::string> 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<SMuteArgs>({.name = "unmute", .parser = [](const std::string &args) -> std::pair<SMuteArgs, bool> {
+ 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();