irc_client.cpp 14.1 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
bool IrcClient::is_channel_joined(const std::string& name)
{
louiz’'s avatar
louiz’ committed
61 62
  IrcChannel* channel = this->get_channel(name);
  return channel->joined;
louiz’'s avatar
louiz’ committed
63 64
}

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

306 307 308 309 310 311 312 313 314 315 316 317 318
void IrcClient::on_nickname_conflict(const IrcMessage& message)
{
  const std::string nickname = message.arguments[1];
  this->on_generic_error(message);
  for (auto it = this->channels.begin(); it != this->channels.end(); ++it)
  {
    Iid iid;
    iid.chan = it->first;
    iid.server = this->hostname;
    this->bridge->send_nickname_conflict_error(iid, nickname);
  }
}

319 320 321 322 323 324 325
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
326 327 328 329 330 331 332 333
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
334 335 336

void IrcClient::on_part(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
337
  const std::string chan_name = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
338
  IrcChannel* channel = this->get_channel(chan_name);
339 340
  if (!channel->joined)
    return ;
louiz’'s avatar
louiz’ committed
341 342 343 344 345 346 347 348 349 350 351 352 353 354
  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)
louiz’'s avatar
louiz’ committed
355
      {
louiz’'s avatar
louiz’ committed
356
        channel->joined = false;
louiz’'s avatar
louiz’ committed
357 358 359 360
        this->channels.erase(chan_name);
        // channel pointer is now invalid
        channel = nullptr;
      }
louiz’'s avatar
louiz’ committed
361 362
    }
}
louiz’'s avatar
louiz’ committed
363

louiz’'s avatar
louiz’ committed
364 365 366 367 368 369 370 371 372 373
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();
374 375
    if (!channel->joined)
      continue;
louiz’'s avatar
louiz’ committed
376 377 378
    std::string own_nick = channel->get_self()->nick;
    this->bridge->send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true);
  }
louiz’'s avatar
louiz’ committed
379
  this->channels.clear();
380
  this->send_gateway_message(std::string("ERROR: ") + leave_message);
louiz’'s avatar
louiz’ committed
381 382
}

louiz’'s avatar
louiz’ committed
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
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
400
          this->bridge->send_muc_leave(std::move(iid), std::move(nick), txt, false);
louiz’'s avatar
louiz’ committed
401 402 403
        }
    }
}
louiz’'s avatar
louiz’ committed
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430

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

431 432 433 434
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
435
  const std::string chan_name = utils::tolower(message.arguments[0]);
436
  IrcChannel* channel = this->get_channel(chan_name);
437 438
  if (!channel->joined)
    return ;
439 440 441 442 443 444 445 446 447
  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
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462
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;
463
  iid.chan = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
464 465
  iid.server = this->hostname;
  IrcUser user(message.prefix);
466 467 468 469 470 471 472 473 474 475
  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
476
  this->bridge->send_message(iid, "", std::string("Mode ") + iid.chan +
477
                                      " [" + mode_arguments + "] by " + user.nick,
louiz’'s avatar
louiz’ committed
478 479 480 481 482 483 484 485 486
                             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] + "]");
}