irc_client.cpp 13.6 KB
Newer Older
1
#include <irc/irc_message.hpp>
2 3 4 5 6
#include <irc/irc_client.hpp>
#include <bridge/bridge.hpp>
#include <irc/irc_user.hpp>

#include <utils/make_unique.hpp>
louiz’'s avatar
louiz’ committed
7
#include <logger/logger.hpp>
louiz’'s avatar
louiz’ committed
8
#include <utils/tolower.hpp>
9
#include <utils/split.hpp>
10 11 12 13

#include <iostream>
#include <stdexcept>

14 15 16
IrcClient::IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge):
  hostname(hostname),
  username(username),
louiz’'s avatar
louiz’ committed
17 18 19
  current_nick(username),
  bridge(bridge),
  welcomed(false)
20 21 22 23 24 25 26
{
}

IrcClient::~IrcClient()
{
}

27 28 29 30 31
void IrcClient::start()
{
  this->connect(this->hostname, "6667");
}

louiz’'s avatar
louiz’ committed
32 33
void IrcClient::on_connected()
{
34 35
  this->send_nick_command(this->username);
  this->send_user_command(this->username, this->username);
36
  this->send_gateway_message("Connected to IRC server.");
louiz’'s avatar
louiz’ committed
37 38
}

39 40
void IrcClient::on_connection_close()
{
41 42 43
  static const std::string message = "Connection closed by remote server.";
  this->send_gateway_message(message);
  log_warning(message);
44 45
}

46 47 48 49 50 51 52 53 54 55 56 57 58
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();
    }
}

louiz’'s avatar
louiz’ committed
59 60 61 62 63 64
bool IrcClient::is_channel_joined(const std::string& name)
{
  IrcChannel* client = this->get_channel(name);
  return client->joined;
}

louiz’'s avatar
louiz’ committed
65 66 67 68 69
std::string IrcClient::get_own_nick() const
{
  return this->current_nick;
}

70 71
void IrcClient::parse_in_buffer()
{
louiz’'s avatar
louiz’ committed
72 73 74 75 76 77
  while (true)
    {
      auto pos = this->in_buf.find("\r\n");
      if (pos == std::string::npos)
        break ;
      IrcMessage message(this->in_buf.substr(0, pos));
louiz’'s avatar
louiz’ committed
78
      log_debug("IRC RECEIVING: " << message);
louiz’'s avatar
louiz’ committed
79
      this->in_buf = this->in_buf.substr(pos + 2, std::string::npos);
80 81 82 83
      auto cb = irc_callbacks.find(message.command);
      if (cb != irc_callbacks.end())
        (this->*(cb->second))(message);
      else
louiz’'s avatar
louiz’ committed
84
        log_info("No handler for command " << message.command);
louiz’'s avatar
louiz’ committed
85 86 87 88 89
    }
}

void IrcClient::send_message(IrcMessage&& message)
{
louiz’'s avatar
louiz’ committed
90
  log_debug("IRC SENDING: " << message);
louiz’'s avatar
louiz’ committed
91 92 93 94 95 96
  std::string res;
  if (!message.prefix.empty())
    res += ":" + std::move(message.prefix) + " ";
  res += std::move(message.command);
  for (const std::string& arg: message.arguments)
    {
louiz’'s avatar
:3  
louiz’ committed
97
      if (arg.find(" ") != std::string::npos ||
louiz’'s avatar
louiz’ committed
98
          (!arg.empty() && arg[0] == ':'))
louiz’'s avatar
louiz’ committed
99 100 101 102 103 104 105
        {
          res += " :" + arg;
          break;
        }
      res += " " + arg;
    }
  res += "\r\n";
106
  this->send_data(std::move(res));
louiz’'s avatar
louiz’ committed
107 108 109 110
}

void IrcClient::send_user_command(const std::string& username, const std::string& realname)
{
louiz’'s avatar
louiz’ committed
111
  this->send_message(IrcMessage("USER", {username, "ignored", "ignored", realname}));
louiz’'s avatar
louiz’ committed
112 113 114 115 116 117 118
}

void IrcClient::send_nick_command(const std::string& nick)
{
  this->send_message(IrcMessage("NICK", {nick}));
}

119 120 121 122 123
void IrcClient::send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason)
{
  this->send_message(IrcMessage("KICK", {chan_name, target, reason}));
}

louiz’'s avatar
louiz’ committed
124 125 126 127 128
void IrcClient::send_quit_command()
{
  this->send_message(IrcMessage("QUIT", {"gateway shutdown"}));
}

louiz’'s avatar
louiz’ committed
129 130
void IrcClient::send_join_command(const std::string& chan_name)
{
131 132
  if (!this->connected)
    this->start();
louiz’'s avatar
louiz’ committed
133
  if (this->welcomed == false)
louiz’'s avatar
louiz’ committed
134 135 136
    this->channels_to_join.push_back(chan_name);
  else
    this->send_message(IrcMessage("JOIN", {chan_name}));
137 138
}

louiz’'s avatar
louiz’ committed
139
bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body)
140
{
louiz’'s avatar
louiz’ committed
141 142 143
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel->joined == false)
    {
louiz’'s avatar
louiz’ committed
144
      log_warning("Cannot send message to channel " << chan_name << ", it is not joined");
louiz’'s avatar
louiz’ committed
145 146
      return false;
    }
147 148 149 150 151 152 153 154 155
  // Cut the message body into 400-bytes parts (because the whole command
  // must fit into 512 bytes, that's an easy way to make sure the chan name
  // + body fits. I’m lazy.)
  std::string::size_type pos = 0;
  while (pos < body.size())
    {
      this->send_message(IrcMessage("PRIVMSG", {chan_name, body.substr(pos, 400)}));
      pos += 400;
    }
louiz’'s avatar
louiz’ committed
156 157 158
  return true;
}

louiz’'s avatar
louiz’ committed
159 160
void IrcClient::send_private_message(const std::string& username, const std::string& body)
{
161 162 163 164 165 166 167
  std::string::size_type pos = 0;
  while (pos < body.size())
    {
      this->send_message(IrcMessage("PRIVMSG", {username, body.substr(pos, 400)}));
      pos += 400;
    }

louiz’'s avatar
louiz’ committed
168 169
}

louiz’'s avatar
louiz’ committed
170 171 172 173
void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message)
{
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel->joined == true)
louiz’'s avatar
louiz’ committed
174 175 176 177 178 179 180 181 182
    this->send_message(IrcMessage("PART", {chan_name, status_message}));
}

void IrcClient::send_mode_command(const std::string& chan_name, const std::vector<std::string>& arguments)
{
  std::vector<std::string> args(arguments);
  args.insert(args.begin(), chan_name);
  IrcMessage m("MODE", std::move(args));
  this->send_message(std::move(m));
louiz’'s avatar
louiz’ committed
183 184
}

louiz’'s avatar
louiz’ committed
185 186 187 188
void IrcClient::send_pong_command(const IrcMessage& message)
{
  const std::string id = message.arguments[0];
  this->send_message(IrcMessage("PONG", {id}));
189 190 191 192 193 194 195 196 197 198
}

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);
}

199 200 201 202 203
void IrcClient::send_gateway_message(const std::string& message, const std::string& from)
{
  this->bridge->send_xmpp_message(this->hostname, from, message);
}

204 205
void IrcClient::set_and_forward_user_list(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
206
  const std::string chan_name = utils::tolower(message.arguments[2]);
207 208 209 210
  IrcChannel* channel = this->get_channel(chan_name);
  std::vector<std::string> nicks = utils::split(message.arguments[3], ' ');
  for (const std::string& nick: nicks)
    {
211
      const IrcUser* user = channel->add_user(nick);
212 213
      if (user->nick != channel->get_self()->nick)
        {
louiz’'s avatar
louiz’ committed
214
          log_debug("Adding user [" << nick << "] to chan " << chan_name);
215
          this->bridge->send_user_join(this->hostname, chan_name, user);
216 217 218 219
        }
    }
}

louiz’'s avatar
louiz’ committed
220
void IrcClient::on_channel_join(const IrcMessage& message)
221
{
louiz’'s avatar
louiz’ committed
222
  const std::string chan_name = utils::tolower(message.arguments[0]);
223
  IrcChannel* channel = this->get_channel(chan_name);
louiz’'s avatar
louiz’ committed
224 225 226 227 228 229 230 231
  const std::string nick = message.prefix;
  if (channel->joined == false)
    {
      channel->joined = true;
      channel->set_self(nick);
    }
  else
    {
232 233
      const IrcUser* user = channel->add_user(nick);
      this->bridge->send_user_join(this->hostname, chan_name, user);
louiz’'s avatar
louiz’ committed
234
    }
235 236
}

louiz’'s avatar
louiz’ committed
237 238 239 240 241
void IrcClient::on_channel_message(const IrcMessage& message)
{
  const IrcUser user(message.prefix);
  const std::string nick = user.nick;
  Iid iid;
242
  iid.chan = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
243 244
  iid.server = this->hostname;
  const std::string body = message.arguments[1];
louiz’'s avatar
louiz’ committed
245 246 247 248 249 250
  bool muc = true;
  if (!this->get_channel(iid.chan)->joined)
    {
      iid.chan = nick;
      muc = false;
    }
251 252 253 254 255 256 257 258
  if (!body.empty() && body[0] == '\01')
    {
      if (body.substr(1, 6) == "ACTION")
        this->bridge->send_message(iid, nick,
                  std::string("/me") + body.substr(7, body.size() - 8), muc);
    }
  else
    this->bridge->send_message(iid, nick, body, muc);
louiz’'s avatar
louiz’ committed
259 260
}

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
void IrcClient::empty_motd(const IrcMessage& message)
{
  (void)message;
  this->motd.erase();
}

void IrcClient::on_motd_line(const IrcMessage& message)
{
  const std::string body = message.arguments[1];
  // We could send the MOTD without a line break between each IRC-message,
  // but sometimes it contains some ASCII art, we use line breaks to keep
  // them intact.
  this->motd += body+"\n";
}

void IrcClient::send_motd(const IrcMessage& message)
{
  (void)message;
  this->bridge->send_xmpp_message(this->hostname, "", this->motd);
}

282 283
void IrcClient::on_topic_received(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
284
  const std::string chan_name = utils::tolower(message.arguments[1]);
285 286
  IrcChannel* channel = this->get_channel(chan_name);
  channel->topic = message.arguments[2];
louiz’'s avatar
louiz’ committed
287 288
  if (channel->joined)
    this->bridge->send_topic(this->hostname, chan_name, channel->topic);
289 290 291 292
}

void IrcClient::on_channel_completely_joined(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
293
  const std::string chan_name = utils::tolower(message.arguments[1]);
294 295 296
  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);
297
}
louiz’'s avatar
louiz’ committed
298

299 300 301 302 303 304 305 306 307 308 309 310 311 312
void IrcClient::on_erroneous_nickname(const IrcMessage& message)
{
  const std::string error_msg = message.arguments.size() >= 3 ?
    message.arguments[2]: "Erroneous nickname";
  this->send_gateway_message(error_msg + ": " + message.arguments[1], message.prefix);
}

void IrcClient::on_generic_error(const IrcMessage& message)
{
  const std::string error_msg = message.arguments.size() >= 3 ?
    message.arguments[2]: "Unspecified error";
  this->send_gateway_message(message.arguments[1] + ": " + error_msg, message.prefix);
}

louiz’'s avatar
louiz’ committed
313 314 315 316 317 318 319 320
void IrcClient::on_welcome_message(const IrcMessage& message)
{
  this->current_nick = message.arguments[0];
  this->welcomed = true;
  for (const std::string& chan_name: this->channels_to_join)
    this->send_join_command(chan_name);
  this->channels_to_join.clear();
}
louiz’'s avatar
louiz’ committed
321 322 323

void IrcClient::on_part(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
324
  const std::string chan_name = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
325
  IrcChannel* channel = this->get_channel(chan_name);
326 327
  if (!channel->joined)
    return ;
louiz’'s avatar
louiz’ committed
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
  std::string txt;
  if (message.arguments.size() >= 2)
    txt = message.arguments[1];
  const IrcUser* user = channel->find_user(message.prefix);
  if (user)
    {
      std::string nick = user->nick;
      channel->remove_user(user);
      Iid iid;
      iid.chan = chan_name;
      iid.server = this->hostname;
      bool self = channel->get_self()->nick == nick;
      this->bridge->send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self);
      if (self)
        channel->joined = false;
    }
}
louiz’'s avatar
louiz’ committed
345

louiz’'s avatar
louiz’ committed
346 347 348 349 350 351 352 353 354 355
void IrcClient::on_error(const IrcMessage& message)
{
  const std::string leave_message = message.arguments[0];
  // The user is out of all the channels
  for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
  {
    Iid iid;
    iid.chan = it->first;
    iid.server = this->hostname;
    IrcChannel* channel = it->second.get();
356 357
    if (!channel->joined)
      continue;
louiz’'s avatar
louiz’ committed
358 359 360
    std::string own_nick = channel->get_self()->nick;
    this->bridge->send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true);
  }
361
  this->send_gateway_message(std::string("ERROR: ") + leave_message);
louiz’'s avatar
louiz’ committed
362 363
}

louiz’'s avatar
louiz’ committed
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
void IrcClient::on_quit(const IrcMessage& message)
{
  std::string txt;
  if (message.arguments.size() >= 1)
    txt = message.arguments[0];
  for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
    {
      const std::string chan_name = it->first;
      IrcChannel* channel = it->second.get();
      const IrcUser* user = channel->find_user(message.prefix);
      if (user)
        {
          std::string nick = user->nick;
          channel->remove_user(user);
          Iid iid;
          iid.chan = chan_name;
          iid.server = this->hostname;
louiz’'s avatar
louiz’ committed
381
          this->bridge->send_muc_leave(std::move(iid), std::move(nick), txt, false);
louiz’'s avatar
louiz’ committed
382 383 384
        }
    }
}
louiz’'s avatar
louiz’ committed
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411

void IrcClient::on_nick(const IrcMessage& message)
{
  const std::string new_nick = message.arguments[0];
  for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
    {
      const std::string chan_name = it->first;
      IrcChannel* channel = it->second.get();
      IrcUser* user = channel->find_user(message.prefix);
      if (user)
        {
          std::string old_nick = user->nick;
          Iid iid;
          iid.chan = chan_name;
          iid.server = this->hostname;
          bool self = channel->get_self()->nick == old_nick;
          this->bridge->send_nick_change(std::move(iid), old_nick, new_nick, self);
          user->nick = new_nick;
          if (self)
            {
              channel->get_self()->nick = new_nick;
              this->current_nick = new_nick;
            }
        }
    }
}

412 413 414 415
void IrcClient::on_kick(const IrcMessage& message)
{
  const std::string target = message.arguments[1];
  const std::string reason = message.arguments[2];
louiz’'s avatar
louiz’ committed
416
  const std::string chan_name = utils::tolower(message.arguments[0]);
417
  IrcChannel* channel = this->get_channel(chan_name);
418 419
  if (!channel->joined)
    return ;
420 421 422 423 424 425 426 427 428
  if (channel->get_self()->nick == target)
    channel->joined = false;
  IrcUser author(message.prefix);
  Iid iid;
  iid.chan = chan_name;
  iid.server = this->hostname;
  this->bridge->kick_muc_user(std::move(iid), target, reason, author.nick);
}

louiz’'s avatar
louiz’ committed
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
void IrcClient::on_mode(const IrcMessage& message)
{
  const std::string target = message.arguments[0];
  if (target[0] == '&' || target[0] == '#' ||
      target[0] == '!' || target[0] == '+')
    this->on_channel_mode(message);
  else
    this->on_user_mode(message);
}

void IrcClient::on_channel_mode(const IrcMessage& message)
{
  // For now, just transmit the modes so the user can know what happens
  // TODO, actually interprete the mode.
  Iid iid;
444
  iid.chan = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
445 446
  iid.server = this->hostname;
  IrcUser user(message.prefix);
447 448 449 450 451 452 453 454 455 456
  std::string mode_arguments;
  for (size_t i = 1; i < message.arguments.size(); ++i)
    {
      if (!message.arguments[i].empty())
        {
          if (i != 1)
            mode_arguments += " ";
          mode_arguments += message.arguments[i];
        }
    }
louiz’'s avatar
louiz’ committed
457
  this->bridge->send_message(iid, "", std::string("Mode ") + iid.chan +
458
                                      " [" + mode_arguments + "] by " + user.nick,
louiz’'s avatar
louiz’ committed
459 460 461 462 463 464 465 466 467
                             true);
}

void IrcClient::on_user_mode(const IrcMessage& message)
{
  this->bridge->send_xmpp_message(this->hostname, "",
                                  std::string("User mode for ") + message.arguments[0] +
                                  " is [" + message.arguments[1] + "]");
}