summaryrefslogtreecommitdiff
path: root/src/irc
diff options
context:
space:
mode:
Diffstat (limited to 'src/irc')
-rw-r--r--src/irc/iid.cpp17
-rw-r--r--src/irc/iid.hpp34
-rw-r--r--src/irc/irc_channel.cpp24
-rw-r--r--src/irc/irc_channel.hpp34
-rw-r--r--src/irc/irc_client.cpp105
-rw-r--r--src/irc/irc_client.hpp60
-rw-r--r--src/irc/irc_user.cpp24
-rw-r--r--src/irc/irc_user.hpp24
8 files changed, 318 insertions, 4 deletions
diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp
new file mode 100644
index 0000000..ffc8d88
--- /dev/null
+++ b/src/irc/iid.cpp
@@ -0,0 +1,17 @@
+#include <irc/iid.hpp>
+
+Iid::Iid(const std::string& iid)
+{
+ std::string::size_type sep = iid.find("%");
+ if (sep != std::string::npos)
+ {
+ this->chan = iid.substr(0, sep);
+ sep++;
+ }
+ else
+ {
+ this->chan = iid;
+ return;
+ }
+ this->server = iid.substr(sep);
+}
diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp
new file mode 100644
index 0000000..aacc9e6
--- /dev/null
+++ b/src/irc/iid.hpp
@@ -0,0 +1,34 @@
+#ifndef IID_INCLUDED
+# define IID_INCLUDED
+
+#include <string>
+
+/**
+ * A name representing an IRC channel, on the same model than the XMPP JIDs (but much simpler).
+ * The separator between the server and the channel name is '%'
+ * #test%irc.freenode.org has :
+ * - chan: "#test" (the # is part of the name, it could very well be absent, or & instead
+ * - server: "irc.freenode.org"
+ * #test has:
+ * - chan: "#test"
+ * - server: ""
+ * %irc.freenode.org:
+ * - chan: ""
+ * - server: "irc.freenode.org"
+ */
+class Iid
+{
+public:
+ explicit Iid(const std::string& iid);
+
+ std::string chan;
+ std::string server;
+
+private:
+ Iid(const Iid&) = delete;
+ Iid(Iid&&) = delete;
+ Iid& operator=(const Iid&) = delete;
+ Iid& operator=(Iid&&) = delete;
+};
+
+#endif // IID_INCLUDED
diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp
new file mode 100644
index 0000000..223305b
--- /dev/null
+++ b/src/irc/irc_channel.cpp
@@ -0,0 +1,24 @@
+#include <irc/irc_channel.hpp>
+#include <utils/make_unique.hpp>
+
+IrcChannel::IrcChannel():
+ joined(false),
+ self(nullptr)
+{
+}
+
+void IrcChannel::set_self(const std::string& name)
+{
+ this->self = std::make_unique<IrcUser>(name);
+}
+
+IrcUser* IrcChannel::add_user(const std::string& name)
+{
+ this->users.emplace_back(std::make_unique<IrcUser>(name));
+ return this->users.back().get();
+}
+
+IrcUser* IrcChannel::get_self() const
+{
+ return this->self.get();
+}
diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp
new file mode 100644
index 0000000..da0f298
--- /dev/null
+++ b/src/irc/irc_channel.hpp
@@ -0,0 +1,34 @@
+#ifndef IRC_CHANNEL_INCLUDED
+# define IRC_CHANNEL_INCLUDED
+
+#include <irc/irc_user.hpp>
+#include <memory>
+#include <string>
+#include <vector>
+
+/**
+ * Keep the state of a joined channel (the list of occupants with their
+ * informations (mode, etc), the modes, etc)
+ */
+class IrcChannel
+{
+public:
+ explicit IrcChannel();
+
+ bool joined;
+ std::string topic;
+ void set_self(const std::string& name);
+ IrcUser* get_self() const;
+ IrcUser* add_user(const std::string& name);
+
+private:
+ std::unique_ptr<IrcUser> self;
+ std::vector<std::unique_ptr<IrcUser>> users;
+
+ IrcChannel(const IrcChannel&) = delete;
+ IrcChannel(IrcChannel&&) = delete;
+ IrcChannel& operator=(const IrcChannel&) = delete;
+ IrcChannel& operator=(IrcChannel&&) = delete;
+};
+
+#endif // IRC_CHANNEL_INCLUDED
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index 80f36ee..7875b1c 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -1,10 +1,18 @@
-#include <irc/irc_client.hpp>
#include <irc/irc_message.hpp>
+#include <irc/irc_client.hpp>
+#include <bridge/bridge.hpp>
+#include <irc/irc_user.hpp>
+
+#include <utils/make_unique.hpp>
+#include <utils/split.hpp>
#include <iostream>
#include <stdexcept>
-IrcClient::IrcClient()
+IrcClient::IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge):
+ hostname(hostname),
+ username(username),
+ bridge(bridge)
{
std::cout << "IrcClient()" << std::endl;
}
@@ -14,8 +22,15 @@ IrcClient::~IrcClient()
std::cout << "~IrcClient()" << std::endl;
}
+void IrcClient::start()
+{
+ this->connect(this->hostname, "6667");
+}
+
void IrcClient::on_connected()
{
+ this->send_nick_command(this->username);
+ this->send_user_command(this->username, this->username);
}
void IrcClient::on_connection_close()
@@ -23,6 +38,19 @@ void IrcClient::on_connection_close()
std::cout << "Connection closed by remote server." << std::endl;
}
+IrcChannel* IrcClient::get_channel(const std::string& name)
+{
+ try
+ {
+ return this->channels.at(name).get();
+ }
+ catch (const std::out_of_range& exception)
+ {
+ this->channels.emplace(name, std::make_unique<IrcChannel>());
+ return this->channels.at(name).get();
+ }
+}
+
void IrcClient::parse_in_buffer()
{
while (true)
@@ -33,6 +61,21 @@ void IrcClient::parse_in_buffer()
IrcMessage message(this->in_buf.substr(0, pos));
this->in_buf = this->in_buf.substr(pos + 2, std::string::npos);
std::cout << message << std::endl;
+ // TODO map function and command name properly
+ if (message.command == "PING")
+ this->send_pong_command();
+ else if (message.command == "NOTICE" ||
+ message.command == "375" ||
+ message.command == "372")
+ this->forward_server_message(message);
+ else if (message.command == "JOIN")
+ this->on_self_channel_join(message);
+ else if (message.command == "353")
+ this->set_and_forward_user_list(message);
+ else if (message.command == "332")
+ this->on_topic_received(message);
+ else if (message.command == "366")
+ this->on_channel_completely_joined(message);
}
}
@@ -52,6 +95,8 @@ void IrcClient::send_message(IrcMessage&& message)
res += " " + arg;
}
res += "\r\n";
+ std::cout << "=== IRC SENDING ===" << std::endl;
+ std::cout << res << std::endl;
this->send_data(std::move(res));
}
@@ -67,5 +112,59 @@ void IrcClient::send_nick_command(const std::string& nick)
void IrcClient::send_join_command(const std::string& chan_name)
{
- this->send_message(IrcMessage("JOIN", {chan_name}));
+ IrcChannel* channel = this->get_channel(chan_name);
+ if (channel->joined == false)
+ this->send_message(IrcMessage("JOIN", {chan_name}));
+}
+
+void IrcClient::send_pong_command()
+{
+ this->send_message(IrcMessage("PONG", {}));
+}
+
+void IrcClient::forward_server_message(const IrcMessage& message)
+{
+ const std::string from = message.prefix;
+ const std::string body = message.arguments[1];
+
+ this->bridge->send_xmpp_message(this->hostname, from, body);
+}
+
+void IrcClient::set_and_forward_user_list(const IrcMessage& message)
+{
+ const std::string chan_name = message.arguments[2];
+ IrcChannel* channel = this->get_channel(chan_name);
+ std::vector<std::string> nicks = utils::split(message.arguments[3], ' ');
+ for (const std::string& nick: nicks)
+ {
+ IrcUser* user = channel->add_user(nick);
+ if (user->nick != channel->get_self()->nick)
+ {
+ std::cout << "Adding user [" << nick << "] to chan " << chan_name << std::endl;
+ this->bridge->send_user_join(this->hostname, chan_name, user->nick);
+ }
+ }
+}
+
+void IrcClient::on_self_channel_join(const IrcMessage& message)
+{
+ const std::string chan_name = message.arguments[0];
+ IrcChannel* channel = this->get_channel(chan_name);
+ channel->joined = true;
+ channel->set_self(message.prefix);
+}
+
+void IrcClient::on_topic_received(const IrcMessage& message)
+{
+ const std::string chan_name = message.arguments[1];
+ IrcChannel* channel = this->get_channel(chan_name);
+ channel->topic = message.arguments[2];
+}
+
+void IrcClient::on_channel_completely_joined(const IrcMessage& message)
+{
+ const std::string chan_name = message.arguments[1];
+ IrcChannel* channel = this->get_channel(chan_name);
+ this->bridge->send_self_join(this->hostname, chan_name, channel->get_self()->nick);
+ this->bridge->send_topic(this->hostname, chan_name, channel->topic);
}
diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp
index e380f5b..db1b83b 100644
--- a/src/irc/irc_client.hpp
+++ b/src/irc/irc_client.hpp
@@ -2,11 +2,16 @@
# define IRC_CLIENT_INCLUDED
#include <irc/irc_message.hpp>
+#include <irc/irc_channel.hpp>
+#include <irc/iid.hpp>
#include <network/socket_handler.hpp>
+#include <unordered_map>
#include <string>
+class Bridge;
+
/**
* Represent one IRC client, i.e. an endpoint connected to a single IRC
* server, through a TCP socket, receiving and sending commands to it.
@@ -16,9 +21,13 @@
class IrcClient: public SocketHandler
{
public:
- explicit IrcClient();
+ explicit IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge);
~IrcClient();
/**
+ * Connect to the IRC server
+ */
+ void start();
+ /**
* Called when successfully connected to the server
*/
void on_connected();
@@ -32,12 +41,20 @@ public:
*/
void parse_in_buffer();
/**
+ * Return the channel with this name, create it if it does not yet exist
+ */
+ IrcChannel* get_channel(const std::string& name);
+ /**
* Serialize the given message into a line, and send that into the socket
* (actually, into our out_buf and signal the poller that we want to wach
* for send events to be ready)
*/
void send_message(IrcMessage&& message);
/**
+ * Send the PONG irc command
+ */
+ void send_pong_command();
+ /**
* Send the USER irc command
*/
void send_user_command(const std::string& username, const std::string& realname);
@@ -49,8 +66,49 @@ public:
* Send the JOIN irc command
*/
void send_join_command(const std::string& chan_name);
+ /**
+ * Forward the server message received from IRC to the XMPP component
+ */
+ void forward_server_message(const IrcMessage& message);
+ /**
+ * Forward the join of an other user into an IRC channel, and save the
+ * IrcUsers in the IrcChannel
+ */
+ void set_and_forward_user_list(const IrcMessage& message);
+ /**
+ * Remember our nick and host, when we are joined to the channel. The list
+ * of user comes after so we do not send the self-presence over XMPP yet.
+ */
+ void on_self_channel_join(const IrcMessage& message);
+ /**
+ * Save the topic in the IrcChannel
+ */
+ void on_topic_received(const IrcMessage& message);
+ /**
+ * The channel has been completely joined (self presence, topic, all names
+ * received etc), send the self presence and topic to the XMPP user.
+ */
+ void on_channel_completely_joined(const IrcMessage& message);
private:
+ /**
+ * The hostname of the server we are connected to.
+ */
+ const std::string hostname;
+ /**
+ * The user name used in the USER irc command
+ */
+ const std::string username;
+ /**
+ * Raw pointer because the bridge owns us.
+ */
+ Bridge* bridge;
+
+ /**
+ * The list of joined channels, indexed by name
+ */
+ std::unordered_map<std::string, std::unique_ptr<IrcChannel>> channels;
+
IrcClient(const IrcClient&) = delete;
IrcClient(IrcClient&&) = delete;
IrcClient& operator=(const IrcClient&) = delete;
diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp
new file mode 100644
index 0000000..fc853bc
--- /dev/null
+++ b/src/irc/irc_user.cpp
@@ -0,0 +1,24 @@
+#include <irc/irc_user.hpp>
+
+#include <iostream>
+
+IrcUser::IrcUser(const std::string& name)
+{
+ const std::string::size_type sep = name.find("!");
+ if (sep == std::string::npos)
+ {
+ if (name[0] == '@' || name[0] == '+')
+ this->nick = name.substr(1);
+ else
+ this->nick = name;
+ }
+ else
+ {
+ if (name[0] == '@' || name[0] == '+')
+ this->nick = name.substr(1, sep);
+ else
+ this->nick = name.substr(0, sep);
+ this->host = name.substr(sep+1);
+ }
+ std::cout << "Created user: [" << this->nick << "!" << this->host << std::endl;
+}
diff --git a/src/irc/irc_user.hpp b/src/irc/irc_user.hpp
new file mode 100644
index 0000000..b76b2ef
--- /dev/null
+++ b/src/irc/irc_user.hpp
@@ -0,0 +1,24 @@
+#ifndef IRC_USER_INCLUDED
+# define IRC_USER_INCLUDED
+
+#include <string>
+
+/**
+ * Keeps various information about one IRC channel user
+ */
+class IrcUser
+{
+public:
+ explicit IrcUser(const std::string& name);
+
+ std::string nick;
+ std::string host;
+
+private:
+ IrcUser(const IrcUser&) = delete;
+ IrcUser(IrcUser&&) = delete;
+ IrcUser& operator=(const IrcUser&) = delete;
+ IrcUser& operator=(IrcUser&&) = delete;
+};
+
+#endif // IRC_USER_INCLUDED