irc_client.cpp 11.5 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 <utils/tolower.hpp>
8
#include <utils/split.hpp>
9
10
11
12

#include <iostream>
#include <stdexcept>

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

IrcClient::~IrcClient()
{
  std::cout << "~IrcClient()" << std::endl;
}

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

louiz’'s avatar
louiz’ committed
33
34
void IrcClient::on_connected()
{
35
36
  this->send_nick_command(this->username);
  this->send_user_command(this->username, this->username);
louiz’'s avatar
louiz’ committed
37
38
}

39
40
41
42
43
void IrcClient::on_connection_close()
{
  std::cout << "Connection closed by remote server." << std::endl;
}

44
45
46
47
48
49
50
51
52
53
54
55
56
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
57
58
59
60
61
62
bool IrcClient::is_channel_joined(const std::string& name)
{
  IrcChannel* client = this->get_channel(name);
  return client->joined;
}

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

68
69
void IrcClient::parse_in_buffer()
{
louiz’'s avatar
louiz’ committed
70
71
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));
      this->in_buf = this->in_buf.substr(pos + 2, std::string::npos);
      std::cout << message << std::endl;
78
79
80
81
82
      auto cb = irc_callbacks.find(message.command);
      if (cb != irc_callbacks.end())
        (this->*(cb->second))(message);
      else
        std::cout << "No handler for command " << message.command << std::endl;
louiz’'s avatar
louiz’ committed
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
    }
}

void IrcClient::send_message(IrcMessage&& message)
{
  std::string res;
  if (!message.prefix.empty())
    res += ":" + std::move(message.prefix) + " ";
  res += std::move(message.command);
  for (const std::string& arg: message.arguments)
    {
      if (arg.find(" ") != std::string::npos)
        {
          res += " :" + arg;
          break;
        }
      res += " " + arg;
    }
  res += "\r\n";
102
103
  std::cout << "=== IRC SENDING ===" << std::endl;
  std::cout << res << std::endl;
104
  this->send_data(std::move(res));
louiz’'s avatar
louiz’ committed
105
106
107
108
}

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

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

117
118
119
120
121
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
122
123
void IrcClient::send_join_command(const std::string& chan_name)
{
louiz’'s avatar
louiz’ committed
124
  if (this->welcomed == false)
louiz’'s avatar
louiz’ committed
125
126
127
    this->channels_to_join.push_back(chan_name);
  else
    this->send_message(IrcMessage("JOIN", {chan_name}));
128
129
}

louiz’'s avatar
louiz’ committed
130
bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body)
131
{
louiz’'s avatar
louiz’ committed
132
133
134
135
136
137
138
139
140
141
  IrcChannel* channel = this->get_channel(chan_name);
  if (channel->joined == false)
    {
      std::cout << "Cannot send message to channel " << chan_name << ", it is not joined" << std::endl;
      return false;
    }
  this->send_message(IrcMessage("PRIVMSG", {chan_name, body}));
  return true;
}

louiz’'s avatar
louiz’ committed
142
143
144
145
146
void IrcClient::send_private_message(const std::string& username, const std::string& body)
{
  this->send_message(IrcMessage("PRIVMSG", {username, body}));
}

louiz’'s avatar
louiz’ committed
147
148
149
150
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
151
152
153
154
155
156
157
158
159
    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
160
161
}

louiz’'s avatar
louiz’ committed
162
163
164
165
void IrcClient::send_pong_command(const IrcMessage& message)
{
  const std::string id = message.arguments[0];
  this->send_message(IrcMessage("PONG", {id}));
166
167
168
169
170
171
172
173
174
175
176
177
}

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)
{
louiz’'s avatar
louiz’ committed
178
  const std::string chan_name = utils::tolower(message.arguments[2]);
179
180
181
182
183
184
185
186
187
188
189
190
191
  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);
        }
    }
}

louiz’'s avatar
louiz’ committed
192
void IrcClient::on_channel_join(const IrcMessage& message)
193
{
louiz’'s avatar
louiz’ committed
194
  const std::string chan_name = utils::tolower(message.arguments[0]);
195
  IrcChannel* channel = this->get_channel(chan_name);
louiz’'s avatar
louiz’ committed
196
197
198
199
200
201
202
203
204
205
206
  const std::string nick = message.prefix;
  if (channel->joined == false)
    {
      channel->joined = true;
      channel->set_self(nick);
    }
  else
    {
      IrcUser* user = channel->add_user(nick);
      this->bridge->send_user_join(this->hostname, chan_name, user->nick);
    }
207
208
}

louiz’'s avatar
louiz’ committed
209
210
211
212
213
214
215
216
void IrcClient::on_channel_message(const IrcMessage& message)
{
  const IrcUser user(message.prefix);
  const std::string nick = user.nick;
  Iid iid;
  iid.chan = message.arguments[0];
  iid.server = this->hostname;
  const std::string body = message.arguments[1];
louiz’'s avatar
louiz’ committed
217
218
219
220
221
222
  bool muc = true;
  if (!this->get_channel(iid.chan)->joined)
    {
      iid.chan = nick;
      muc = false;
    }
223
224
225
226
227
228
229
230
  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
231
232
}

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
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);
}

254
255
void IrcClient::on_topic_received(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
256
  const std::string chan_name = utils::tolower(message.arguments[1]);
257
258
259
260
261
262
  IrcChannel* channel = this->get_channel(chan_name);
  channel->topic = message.arguments[2];
}

void IrcClient::on_channel_completely_joined(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
263
  const std::string chan_name = utils::tolower(message.arguments[1]);
264
265
266
  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);
267
}
louiz’'s avatar
louiz’ committed
268
269
270
271
272
273
274
275
276

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
277
278
279

void IrcClient::on_part(const IrcMessage& message)
{
louiz’'s avatar
louiz’ committed
280
  const std::string chan_name = utils::tolower(message.arguments[0]);
louiz’'s avatar
louiz’ committed
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
  IrcChannel* channel = this->get_channel(chan_name);
  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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316

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
317
          this->bridge->send_muc_leave(std::move(iid), std::move(nick), txt, false);
louiz’'s avatar
louiz’ committed
318
319
320
        }
    }
}
louiz’'s avatar
louiz’ committed
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347

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

348
349
350
351
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
352
  const std::string chan_name = utils::tolower(message.arguments[0]);
353
354
355
356
357
358
359
360
361
362
  IrcChannel* channel = this->get_channel(chan_name);
  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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
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;
  iid.chan = message.arguments[0];
  iid.server = this->hostname;
  IrcUser user(message.prefix);
  this->bridge->send_message(iid, "", std::string("Mode ") + iid.chan +
                                      " [" + message.arguments[1] +
                                      (message.arguments.size() > 2 ? (" " + message.arguments[2]): "")
                                      + "] by " + user.nick,
                             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] + "]");
}