diff --git a/.gitignore b/.gitignore index 5f11ccd..55fc558 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,6 @@ # Other files ircserv +bot .DS_Store .vscode \ No newline at end of file diff --git a/Makefile b/Makefile index 921a592..f13a351 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ NAME = ircserv +BOT = bot CXX = c++ FLAGS = -Wall -Werror -Wextra -std=c++98 RM = rm -rf @@ -9,7 +10,7 @@ PASS ?= 1111 # ================================================================================= INCLUDES_PATH = includes -INCLUDES_SUBDIRS = commands parser exceptions +INCLUDES_SUBDIRS = commands parser exceptions irc_bot INCLUDES_DIRS = $(INCLUDES_PATH) $(addprefix $(INCLUDES_PATH)/, $(INCLUDES_SUBDIRS)) HEADERS = $(addprefix -I , $(INCLUDES_DIRS)) @@ -19,6 +20,10 @@ HEADERS = $(addprefix -I , $(INCLUDES_DIRS)) SRC_DIR = src CMD_DIR = $(SRC_DIR)/commands/ PARSER_DIR = $(SRC_DIR)/parser/ +BOT_DIR = $(SRC_DIR)/irc_bot +BOT_MSG_DIR = $(BOT_DIR)/message/ +BOT_RESP_DIR = $(BOT_DIR)/response/ + CMD_PREFIXS = A User Nick Pass Quit PrivateMessage Join Part Invite Mode Kick Topic Notice CMD_FILES = $(addsuffix Command, $(CMD_PREFIXS)) @@ -30,17 +35,20 @@ PARSER_SRCS = $(addprefix $(PARSER_DIR), $(PARSER_FILES)) FILES = main Server User Channel Utils Logger Responses -SRCS_PATHS = $(addprefix $(SRC_DIR)/, $(FILES)) $(CMD_SRCS) $(PARSER_SRCS) -SRCS = $(addsuffix .cpp, $(SRCS_PATHS)) +SRCS_PATHS = $(addprefix $(SRC_DIR)/, $(FILES)) $(CMD_SRCS) $(PARSER_SRCS) +SRCS = $(addsuffix .cpp, $(SRCS_PATHS)) + +# ================================================================================= -BPREFIXS = Who Down Up +# Bonus part (file transfer) +BPREFIXS = Who Down Up BCMD_FILES = $(addsuffix Command, $(BPREFIXS)) BCMD_SRCS = $(addprefix $(CMD_DIR), $(BCMD_FILES)) BPARSER_FILES = $(addsuffix Parser, $(BPREFIXS)) BPARSER_SRCS = $(addprefix $(PARSER_DIR), $(BPARSER_FILES)) -BSRCS_PATHS = $(BCMD_SRCS) $(BPARSER_SRCS) +BSRCS_PATHS = $(BCMD_SRCS) $(BPARSER_SRCS) BSRCS = $(addsuffix .cpp, $(BSRCS_PATHS)) OBJS = $(SRCS:.cpp=.o) @@ -48,6 +56,23 @@ BOBJS = $(BSRCS:.cpp=.o) # ================================================================================= +# Bonus part (bot) +BOT_MSG_PREFIXS = Message SenderEntity +BOT_MSG_FILES = $(addsuffix .cpp, $(addprefix $(BOT_MSG_DIR), $(BOT_MSG_PREFIXS))) + +BOT_RESP_PREFIXS = ResponseBuilder +BOT_RESP_FILES = $(addsuffix .cpp, $(addprefix $(BOT_RESP_DIR), $(BOT_RESP_PREFIXS))) + +BOT_FILES = GlaskBot IRCClient bot +BOT_SRCS = $(BOT_MSG_FILES) $(BOT_RESP_FILES) $(addsuffix .cpp, $(addprefix $(BOT_DIR)/, $(BOT_FILES))) + +#BOT_SRCS = src/irc_bot/GlaskBot.cpp src/irc_bot/IRCClient.cpp src/irc_bot/bot.cpp\ + src/irc_bot/message/Message.cpp src/irc_bot/message/SenderEntity.cpp\ + src/irc_bot/response/ResponseBuilder.cpp +BOT_OBJS = $(BOT_SRCS:.cpp=.o) + +# ================================================================================= + all: $(NAME) %.o: %.cpp @@ -56,10 +81,10 @@ all: $(NAME) $(NAME): $(OBJS) $(CXX) $(FLAGS) $(HEADERS) $(SRCS) -o $(NAME) -clean: +clean: bot_clean $(RM) $(OBJS) $(BOBJS) files -fclean: clean +fclean: clean bot_fclean $(RM) $(NAME) re: fclean all @@ -72,7 +97,7 @@ a: $(NAME) clear ./$(NAME) $(PORT) $(PASS) -bonus: $(OBJS) $(BOBJS) +bonus: $(OBJS) $(BOBJS) $(BOT) $(CXX) $(FLAGS) -D BONUS $(HEADERS) $(SRCS) $(BSRCS) -o $(NAME) $(RM) files mkdir files @@ -85,6 +110,17 @@ ba: bonus clear ./$(NAME) $(PORT) $(PASS) -bre: fclean bonus +bre: fclean bonus $(BOT) + +# Bonus rules (bot) + +$(BOT): $(BOT_OBJS) + $(CXX) $(FLAGS) $(HEADERS) $(BOT_OBJS) -o $(BOT) + +bot_clean: + $(RM) $(BOT_OBJS) + +bot_fclean: bot_clean + $(RM) $(BOT) -.PHONY: a all ba be bre bonus clean e fclean re +.PHONY: a all ba be bre bonus clean e fclean re bot_clean bot_fclean \ No newline at end of file diff --git a/includes/irc_bot/BadUsageException.hpp b/includes/irc_bot/BadUsageException.hpp new file mode 100644 index 0000000..dd7d681 --- /dev/null +++ b/includes/irc_bot/BadUsageException.hpp @@ -0,0 +1,31 @@ +#ifndef BAD_USAGE_EXCEPTION_HPP +# define BAD_USAGE_EXCEPTION_HPP + +# include +# include + +# define USAGE_EXCEPTION_MESSAGE(message) "usage: " + (message) + +/** + * Exception thrown when a command is used incorrectly + */ +class BadUsageException : public std::exception { + private: + std::string _message; + std::string _to; + public: + BadUsageException(const std::string &to, const std::string &message) + : _message(USAGE_EXCEPTION_MESSAGE(message)), _to(to) {}; + + virtual ~BadUsageException() throw() {} + + virtual const char *what() const throw() { + return _message.c_str(); + }; + + const std::string &getTo() const { + return _to; + } +}; + +#endif \ No newline at end of file diff --git a/includes/irc_bot/GlaskBot.hpp b/includes/irc_bot/GlaskBot.hpp new file mode 100644 index 0000000..a47659b --- /dev/null +++ b/includes/irc_bot/GlaskBot.hpp @@ -0,0 +1,38 @@ +#ifndef GLASK_BOT_HPP +# define GLASK_BOT_HPP + +# include +# include +# include + +# include "IRCClient.hpp" +# include "Message.hpp" +# include "ResponseBuilder.hpp" +# include "BadUsageException.hpp" + +# define BOT_NAME "glask" + +# define BOT_JOINNING_MESSAGE "Uoola a todos!! Soy GlaskBot, un bot creado por el grupo de desarrollo de Glask. Si necesitas ayuda, no dudes en preguntar. ¡Estoy aquí para ayudarte!" + +/** + * This class aims to provide a simple interface to interact with an IRC server. + */ +class GlaskBot : public IRCClient { + private: + static std::vector split(const std::string &response); + std::map welcomeMessages; // + void onPrivateMessage(const Message &message); + void onJoin(const Message &message); + + void joinChannel(const std::string &destination, const std::vector &messageParts); + void setMessage(const std::vector &messageParts); + + bool isChannel(const std::string &receiver); + + public: + GlaskBot(const std::string &address, int port, const std::string &password); + virtual ~GlaskBot(); + void handleResponse(const Message &message); +}; + +#endif \ No newline at end of file diff --git a/includes/irc_bot/IRCClient.hpp b/includes/irc_bot/IRCClient.hpp new file mode 100644 index 0000000..bdd24d5 --- /dev/null +++ b/includes/irc_bot/IRCClient.hpp @@ -0,0 +1,36 @@ +#ifndef IRC_CLIENT_HPP +# define IRC_CLIENT_HPP + +# include +# include +# include +# include +# include +# include +# include "Message.hpp" + +# define BUFFER_SIZE 1024 + +/** + * This class aims to provide a simple interface to interact with an IRC server. + */ +class IRCClient { + private: + bool _connectionEstablished; + int _sock; + const std::string &_address; + int port; + + void conn(); + void receive(); + + public: + IRCClient(const std::string &, int); + virtual ~IRCClient(); + bool sendData(const std::string &); + void startLoop(); + + virtual void handleResponse(const Message &message) = 0; +}; + +#endif \ No newline at end of file diff --git a/includes/irc_bot/MalformedException.hpp b/includes/irc_bot/MalformedException.hpp new file mode 100644 index 0000000..5e467db --- /dev/null +++ b/includes/irc_bot/MalformedException.hpp @@ -0,0 +1,25 @@ +#ifndef MALFORMED_EXCEPTION_HPP +# define MALFORMED_EXCEPTION_HPP + +# include +# include + +# define MALFORMED_EXCEPTION_MESSAGE(message) "Malformed message: " + (message) + +/** + * Exception thrown when a message is malformed + */ +class MalformedException : public std::exception { + private: + std::string _message; + public: + MalformedException(const std::string &message) + : _message(MALFORMED_EXCEPTION_MESSAGE(message)) {}; + + virtual ~MalformedException() throw() {} + + virtual const char *what() const throw() { + return _message.c_str(); + }; +}; +#endif \ No newline at end of file diff --git a/includes/irc_bot/Message.hpp b/includes/irc_bot/Message.hpp new file mode 100644 index 0000000..c6aa2eb --- /dev/null +++ b/includes/irc_bot/Message.hpp @@ -0,0 +1,28 @@ +#ifndef MESSAGE_HPP +# define MESSAGE_HPP + +# include +# include +# include +# include +# include "SenderEntity.hpp" + +/** + * This class represents a message received from an IRC server. + */ +class Message { + private: + SenderEntity *_sender; + std::string _command; + std::string _params; + std::vector splitParams(const std::string &response); + + public: + Message(const std::string &senderData); + ~Message(); + const SenderEntity *getSender() const; + const std::string &getCommand() const; + const std::string &getParams() const; +}; + +#endif \ No newline at end of file diff --git a/includes/irc_bot/ResponseBuilder.hpp b/includes/irc_bot/ResponseBuilder.hpp new file mode 100644 index 0000000..03a1d6c --- /dev/null +++ b/includes/irc_bot/ResponseBuilder.hpp @@ -0,0 +1,24 @@ +#ifndef RESPONSE_BUILDER_HPP +# define RESPONSE_BUILDER_HPP + +# include + +/** + * This class is responsible for building responses to the IRC server. + */ +class ResponseBuilder { + private: + ResponseBuilder(); + + public: + static std::string join(const std::string &channel); + static std::string part(const std::string &channel); + static std::string privmsg(const std::string &destination, const std::string &message); + static std::string user(const std::string &username, const std::string &hostname, const std::string &serverName, const std::string &realName); + static std::string nick(const std::string &nick); + static std::string pass(const std::string &password); + + ~ResponseBuilder(); +}; + +#endif \ No newline at end of file diff --git a/includes/irc_bot/SenderEntity.hpp b/includes/irc_bot/SenderEntity.hpp new file mode 100644 index 0000000..0361d2e --- /dev/null +++ b/includes/irc_bot/SenderEntity.hpp @@ -0,0 +1,28 @@ +#ifndef SENDER_ENTITY_HPP +# define SENDER_ENTITY_HPP + +# include +# include "MalformedException.hpp" + +/** + * This class represents the entity that sent a message. + */ +class SenderEntity { + private: + bool _isServer; + std::string _serverName; + std::string _nickname; + std::string _username; + std::string _hostname; + + public: + SenderEntity(const std::string &senderData); + + bool isServer() const; + const std::string &getServerName() const; + const std::string &getNickname() const; + const std::string &getHostname() const; + const std::string &getUsername() const; +}; + +#endif \ No newline at end of file diff --git a/src/irc_bot/GlaskBot.cpp b/src/irc_bot/GlaskBot.cpp new file mode 100644 index 0000000..fd63235 --- /dev/null +++ b/src/irc_bot/GlaskBot.cpp @@ -0,0 +1,159 @@ +#include "GlaskBot.hpp" + +/** + * GlaskBot constructor + * + * @param address The address of the server + * @param port The port of the server + * @param password The password to connect to the server + */ +GlaskBot::GlaskBot(const std::string &address, int port, const std::string &password) : IRCClient(address, port) { + if (!password.empty()) + this->sendData(ResponseBuilder::pass(password)); + this->sendData(ResponseBuilder::nick(BOT_NAME)); + this->sendData(ResponseBuilder::user(BOT_NAME, "*", "*", BOT_NAME + std::string("Bot"))); + this->startLoop(); +} + +/** + * GlaskBot destructor + */ +GlaskBot::~GlaskBot() {} + +/** + * This function aims to handle the response from the server. + * + * @param message The message + */ +void GlaskBot::handleResponse(const Message &message) { + try { + if (message.getSender()->isServer()) { + // De momento no handleamos los mensajes del servidor. Gracias por su visita. Por favor, cierra la puerta al salir. + } else { + if (message.getCommand() == "PRIVMSG") + this->onPrivateMessage(message); + else if (message.getCommand() == "JOIN") + this->onJoin(message); + } + } catch (const BadUsageException &e) { + this->sendData(ResponseBuilder::privmsg(e.getTo(), e.what())); + } +} + +/** + * This function aims to handle the private messages. + * + * @param message The message + */ +void GlaskBot::onPrivateMessage(const Message &message) { + std::string destination = message.getSender()->getNickname(); + std::vector messageParts = GlaskBot::split(message.getParams()); + + if (messageParts.size() < 2) + return ; + + std::string receiver(messageParts[0]); + if (receiver == BOT_NAME) { + if (messageParts[1] == ":!join") + joinChannel(destination, messageParts); + // add here more commands to handle on private chat + } else if (isChannel(receiver)) { + if (messageParts[1] == ":!message") + setMessage(messageParts); + // add here more commands to handle on channels + } +} + +/** + * This function aims to handle the join message. + * + * @param message The message + */ +void GlaskBot::onJoin(const Message &message) { + const SenderEntity *sender = message.getSender(); + std::string channel = message.getParams(); + std::string nickname = sender->getNickname(); + + + if (nickname == BOT_NAME) // Bot is joining the channel (Tell the channel he is joining) + this->sendData(ResponseBuilder::privmsg(channel, BOT_JOINNING_MESSAGE)); + else { // An user is joining the channel (send welcome message if set) + std::string welcomeMessage = welcomeMessages[channel]; + if (!welcomeMessage.empty()) { + if (welcomeMessage.find("{}") != std::string::npos) + welcomeMessage = welcomeMessage.replace(welcomeMessage.find("{}"), 2, nickname); + this->sendData(ResponseBuilder::privmsg(channel, welcomeMessage)); + } + } +} + +/** + * Join a channel. + * + * @param destination The destination + * @param messageParts The message parts + */ +void GlaskBot::joinChannel(const std::string &destination, const std::vector &messageParts) { + std::string channel(messageParts[2]); + if (messageParts.size() != 3) + throw BadUsageException(destination, "!join "); + if (!isChannel(channel)) + channel = "#" + channel; + this->sendData(ResponseBuilder::privmsg(destination, "Joining channel " + channel)); + this->sendData(ResponseBuilder::join(channel)); +} + +/** + * Set the message for the channel. + * + * @param messageParts The message parts + */ +void GlaskBot::setMessage(const std::vector &messageParts) { + std::string channel = messageParts[0]; + if (messageParts.size() < 3) + throw BadUsageException(channel, "!message "); + if (messageParts[2] == "set") { + if (messageParts.size() < 4) + throw BadUsageException(channel, "!message set "); + std::string welcomeMessage; + for (size_t i = 3; i < messageParts.size(); ++i) { + welcomeMessage += messageParts[i]; + if (i != messageParts.size() - 1) + welcomeMessage += " "; + } + welcomeMessages[channel] = welcomeMessage; + this->sendData(ResponseBuilder::privmsg(channel, "Set welcome message for channel " + channel)); + } else if (messageParts[2] == "unset") { + if (messageParts.size() != 3) + throw BadUsageException(channel, "!message unset"); + welcomeMessages.erase(channel); + this->sendData(ResponseBuilder::privmsg(channel, "Unset welcome message for channel " + channel)); + } else throw BadUsageException(channel, "!message "); + +} + +/** + * Splits the string by the delimiter (space). + * + * @param s The string to be split. + */ +std::vector GlaskBot::split(const std::string &s) { + std::vector elems; + std::stringstream ss(s); + std::string item; + + while (std::getline(ss, item, ' ')) { + if (!item.empty()) + elems.push_back(item); + } + return elems; +} + +/** + * Check if the receiver is a channel. + * + * @param receiver The receiver + */ +bool GlaskBot::isChannel(const std::string &receiver) { + return receiver.length() > 0 && (receiver[0] == '#' || receiver[0] == '&'); +} \ No newline at end of file diff --git a/src/irc_bot/IRCClient.cpp b/src/irc_bot/IRCClient.cpp new file mode 100644 index 0000000..3451399 --- /dev/null +++ b/src/irc_bot/IRCClient.cpp @@ -0,0 +1,95 @@ +#include "IRCClient.hpp" + +/** + * Constructor + * + * @param address The address to connect to + * @param port The port to connect to + */ +IRCClient::IRCClient(const std::string &address , int port) + : _connectionEstablished(false), _address(address), port(port) { + conn(); +} + +/** + * Destructor + */ +IRCClient::~IRCClient() { + close(this->_sock); +} + +/** + * Connect to a host on a certain port number + */ +void IRCClient::conn() { + if (this->_connectionEstablished) + return ; + + this->_sock = socket(AF_INET , SOCK_STREAM , 0); + if (this->_sock == -1) { + std::cerr << "Could not create socket" << std::endl; + return ; + } + + struct sockaddr_in server; + server.sin_addr.s_addr = inet_addr(this->_address.c_str()); + server.sin_family = AF_INET; + server.sin_port = htons(port); + std::cout << "Socket created\n" << std::endl; + if (connect(this->_sock , (struct sockaddr *)&server , sizeof(server)) < 0) { + std::cerr << "connect failed. Error" << std::endl; + return ; + } + this->_connectionEstablished = true; + std::cout << "Connected\n"; +} + +/** + * Send data to the connected host + * + * @param data The data to send + */ +bool IRCClient::sendData(const std::string &data) { + const std::string message = data + "\r\n"; + + if (send(this->_sock, message.c_str(), message.size(), 0) < 0) { + std::cout << "Send failed" << std::endl; + return false; + } + std::cout << "Data send\n" << std::endl; + + return true; +} + +/** + * Receive data from the connected host + */ +void IRCClient::receive() { + char buffer[BUFFER_SIZE]; + + std::memset(buffer, '\0', BUFFER_SIZE); + + if (recv(this->_sock, buffer, BUFFER_SIZE, 0) < 0) + std::cout << "recv failed" << std::endl; + else { + std::string message(buffer); + size_t pos = message.find_first_of("\r\n"); + if (pos != std::string::npos) { + while (pos != std::string::npos) { + handleResponse(Message(message.substr(0, pos))); + message = message.substr(pos + 2); + pos = message.find_first_of("\r\n"); + } + } + } +} + +/** + * Starts the loop to receive data from the connected host +*/ +void IRCClient::startLoop() { + while (true) { + receive(); + } +} + \ No newline at end of file diff --git a/src/irc_bot/bot.cpp b/src/irc_bot/bot.cpp new file mode 100644 index 0000000..c4480c7 --- /dev/null +++ b/src/irc_bot/bot.cpp @@ -0,0 +1,39 @@ +#include "GlaskBot.hpp" +#include + +/** + * Validate the arguments. If the arguments are invalid, an exception is thrown. + * + * @param argc The number of arguments + * @param argv The arguments + */ +void validateArgs(int argc, char *argv[]) { + if (argc != 4 && argc != 3) + throw std::invalid_argument("Usage: " + std::string(argv[0]) + " []"); + + std::string portStr = argv[2]; + if (portStr.find_first_not_of("0123456789") != std::string::npos) + throw std::invalid_argument("Invalid port number"); +} + +/** + * Main function + * + * @param argc The number of arguments + * @param argv The arguments + * @return int The exit code + */ +int main(int argc , char *argv[]) { + try { + validateArgs(argc, argv); + std::string hostname = argv[1]; + std::string portStr = argv[2]; + std::string password = argc == 4 ? argv[3] : ""; + int port = std::atoi(portStr.c_str()); + GlaskBot c(hostname, port, password); + } catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + return 1; + } + return 0; +} diff --git a/src/irc_bot/message/Message.cpp b/src/irc_bot/message/Message.cpp new file mode 100644 index 0000000..a320827 --- /dev/null +++ b/src/irc_bot/message/Message.cpp @@ -0,0 +1,70 @@ +#include "Message.hpp" + +/** + * Constructor for the Message class. + * + * @param senderData The sender data. + */ +Message::Message(const std::string &message) { + std::vector messageParts = splitParams(message); + if (messageParts.size() < 2) + throw MalformedException("Message does not have enough params."); + _sender = new SenderEntity(messageParts[0]); + _command = messageParts[1]; + + // TODO: if sender is the server, _param is containing also the client (me, the bot) nickname + // at the beginning, so do we need to remove it? + _params = message.substr(message.find(_command) + _command.size() + 1); +} + +/** + * Destructor for the Message class. + */ +Message::~Message() { + delete _sender; +} + +/** + * Splits the string by the delimiter. + * + * @param s The string to be split. + * + * @return The vector of strings. + */ +std::vector Message::splitParams(const std::string &s) { + std::vector elems; + std::stringstream ss(s); + std::string item; + + while (std::getline(ss, item, ' ')) + if (!item.empty()) + elems.push_back(item); + return elems; +} + +/** + * Returns the sender entity. + * + * @return The sender entity. + */ +const SenderEntity *Message::getSender() const { + return _sender; +} + +/** + * Returns the command. + * + * @return The command. + */ +const std::string &Message::getCommand() const { + return _command; +} + +/** + * Returns the params. + * + * @return The params. + */ +const std::string &Message::getParams() const { + return _params; +} diff --git a/src/irc_bot/message/SenderEntity.cpp b/src/irc_bot/message/SenderEntity.cpp new file mode 100644 index 0000000..f9f059e --- /dev/null +++ b/src/irc_bot/message/SenderEntity.cpp @@ -0,0 +1,46 @@ +#include "SenderEntity.hpp" + +SenderEntity::SenderEntity(const std::string &data) { + std::string senderData = data; + if (senderData[0] != ':') + throw MalformedException("Sender data does not start with a colon."); + senderData = senderData.substr(1); + // if the sender data contains ! and @, it is a user + size_t exclamationMark = senderData.find('!'); + size_t atSign = senderData.find('@'); + + if (exclamationMark != std::string::npos + && atSign != std::string::npos) { + _isServer = false; + _nickname = senderData.substr(0, exclamationMark); + _username = senderData.substr(exclamationMark + 1, atSign - exclamationMark - 1); + _hostname = senderData.substr(atSign + 1); + _serverName = ""; + } else { + _isServer = true; + _nickname = ""; + _username = ""; + _hostname = ""; + _serverName = senderData; + } +} + +bool SenderEntity::isServer() const { + return _isServer; +} + +const std::string &SenderEntity::getServerName() const { + return _serverName; +} + +const std::string &SenderEntity::getNickname() const { + return _nickname; +} + +const std::string &SenderEntity::getUsername() const { + return _username; +} + +const std::string &SenderEntity::getHostname() const { + return _hostname; +} \ No newline at end of file diff --git a/src/irc_bot/response/ResponseBuilder.cpp b/src/irc_bot/response/ResponseBuilder.cpp new file mode 100644 index 0000000..a83650a --- /dev/null +++ b/src/irc_bot/response/ResponseBuilder.cpp @@ -0,0 +1,33 @@ +#include "ResponseBuilder.hpp" + +ResponseBuilder::ResponseBuilder() {} + +ResponseBuilder::~ResponseBuilder() {} + +std::string ResponseBuilder::join(const std::string &channel) { + return "JOIN " + channel; +} + +std::string ResponseBuilder::part(const std::string &channel) { + return "PART " + channel; +} + +std::string ResponseBuilder::privmsg(const std::string &destination, const std::string &message) { + return "PRIVMSG " + destination + " :" + message; +} + +std::string ResponseBuilder::user( + const std::string &username, + const std::string &hostname, + const std::string &serverName, + const std::string &realName) { + return "USER " + username + " " + hostname + " " + serverName + " :" + realName; +} + +std::string ResponseBuilder::nick(const std::string &nick) { + return "NICK " + nick; +} + +std::string ResponseBuilder::pass(const std::string &pass) { + return "PASS " + pass; +} \ No newline at end of file