bridge.cpp 26 KB
Newer Older
1
#include <bridge/bridge.hpp>
2
#include <bridge/list_element.hpp>
3
#include <xmpp/biboumi_component.hpp>
4
#include <network/poller.hpp>
5
#include <utils/empty_if_fixed_server.hpp>
6
#include <utils/encoding.hpp>
7
#include <utils/tolower.hpp>
louiz’'s avatar
louiz’ committed
8
#include <logger/logger.hpp>
9
#include <utils/revstr.hpp>
louiz’'s avatar
louiz’ committed
10
#include <utils/split.hpp>
11
#include <xmpp/jid.hpp>
12
#include <database/database.hpp>
louiz’'s avatar
louiz’ committed
13

louiz’'s avatar
louiz’ committed
14
15
using namespace std::string_literals;

16
17
static const char* action_prefix = "\01ACTION ";

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

static std::string out_encoding_for(const Bridge& bridge, const Iid& iid)
{
#ifdef USE_DATABASE
  const auto jid = bridge.get_bare_jid();
  auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local());
  return options.encodingOut.value();
#else
  return {"ISO-8859-1"};
#endif
}

static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
{
#ifdef USE_DATABASE
  const auto jid = bridge.get_bare_jid();
  auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local());
  return options.encodingIn.value();
#else
  return {"ISO-8859-1"};
#endif
}

41
Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller> poller):
42
43
44
45
46
47
  user_jid(user_jid),
  xmpp(xmpp),
  poller(poller)
{
}

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
 * Return the role and affiliation, corresponding to the given irc mode */
static std::tuple<std::string, std::string> get_role_affiliation_from_irc_mode(const char mode)
{
  if (mode == 'a' || mode == 'q')
    return std::make_tuple("moderator", "owner");
  else if (mode == 'o')
    return std::make_tuple("moderator", "admin");
  else if (mode == 'h')
    return std::make_tuple("moderator", "member");
  else if (mode == 'v')
    return std::make_tuple("participant", "member");
  else
    return std::make_tuple("participant", "none");
}

64
void Bridge::shutdown(const std::string& exit_message)
louiz’'s avatar
louiz’ committed
65
66
67
{
  for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it)
  {
louiz’'s avatar
louiz’ committed
68
69
    it->second->send_quit_command(exit_message);
    it->second->leave_dummy_channel(exit_message);
louiz’'s avatar
louiz’ committed
70
71
72
  }
}

louiz’'s avatar
louiz’ committed
73
74
75
76
77
78
void Bridge::clean()
{
  auto it = this->irc_clients.begin();
  while (it != this->irc_clients.end())
  {
    IrcClient* client = it->second.get();
louiz’'s avatar
louiz’ committed
79
80
    if (!client->is_connected() && !client->is_connecting() &&
        !client->get_resolver().is_resolving())
louiz’'s avatar
louiz’ committed
81
82
83
84
85
86
      it = this->irc_clients.erase(it);
    else
      ++it;
  }
}

87
88
89
90
91
const std::string& Bridge::get_jid() const
{
  return this->user_jid;
}

louiz’'s avatar
louiz’ committed
92
93
94
95
96
97
std::string Bridge::get_bare_jid() const
{
  Jid jid(this->user_jid);
  return jid.local + "@" + jid.domain;
}

98
Xmpp::body Bridge::make_xmpp_body(const std::string& str, const std::string& encoding)
99
100
101
102
103
{
  std::string res;
  if (utils::is_valid_utf8(str.c_str()))
    res = str;
  else
104
    res = utils::convert_to_utf8(str, encoding.data());
105
  return irc_format_to_xhtmlim(res);
106
107
}

108
IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::string& nickname)
109
110
111
112
113
114
115
{
  try
    {
      return this->irc_clients.at(hostname).get();
    }
  catch (const std::out_of_range& exception)
    {
116
117
      auto username = nickname;
      auto realname = nickname;
louiz’'s avatar
louiz’ committed
118
      Jid jid(this->user_jid);
119
120
121
122
123
124
125
126
      if (Config::get("realname_from_jid", "false") == "true")
        {
          username = jid.local;
          realname = this->get_bare_jid();
        }
      this->irc_clients.emplace(hostname,
                                std::make_shared<IrcClient>(this->poller, hostname,
                                                            nickname, username,
louiz’'s avatar
louiz’ committed
127
                                                            realname, jid.domain,
128
                                                            *this));
129
130
131
132
133
      std::shared_ptr<IrcClient> irc = this->irc_clients.at(hostname);
      return irc.get();
    }
}

louiz’'s avatar
louiz’ committed
134
IrcClient* Bridge::get_irc_client(const std::string& hostname)
135
136
137
138
139
140
141
142
143
144
145
146
{
  try
    {
      return this->irc_clients.at(hostname).get();
    }
  catch (const std::out_of_range& exception)
    {
      throw IRCNotConnected(hostname);
    }
}

IrcClient* Bridge::find_irc_client(const std::string& hostname)
louiz’'s avatar
louiz’ committed
147
148
149
150
151
152
153
154
155
156
157
{
  try
    {
      return this->irc_clients.at(hostname).get();
    }
  catch (const std::out_of_range& exception)
    {
      return nullptr;
    }
}

158
bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password)
159
{
160
  IrcClient* irc = this->make_irc_client(iid.get_server(), nickname);
louiz’'s avatar
louiz’ committed
161
  if (iid.get_local().empty())
louiz’'s avatar
louiz’ committed
162
163
164
165
166
167
168
169
170
    { // Join the dummy channel
      if (irc->is_welcomed())
        {
          if (irc->get_dummy_channel().joined)
            return false;
          // Immediately simulate a message coming from the IRC server saying that we
          // joined the channel
          const IrcMessage join_message(irc->get_nick(), "JOIN", {""});
          irc->on_channel_join(join_message);
louiz’'s avatar
louiz’ committed
171
          const IrcMessage end_join_message(std::string(iid.get_server()), "366",
louiz’'s avatar
louiz’ committed
172
173
174
175
176
                                            {irc->get_nick(),
                                                "", "End of NAMES list"});
          irc->on_channel_completely_joined(end_join_message);
        }
      else
177
        {
louiz’'s avatar
louiz’ committed
178
          irc->get_dummy_channel().joining = true;
179
180
          irc->start();
        }
louiz’'s avatar
louiz’ committed
181
182
      return true;
    }
louiz’'s avatar
louiz’ committed
183
  if (irc->is_channel_joined(iid.get_local()) == false)
louiz’'s avatar
louiz’ committed
184
    {
185
      irc->send_join_command(iid.get_local(), password);
louiz’'s avatar
louiz’ committed
186
187
188
      return true;
    }
  return false;
189
190
}

louiz’'s avatar
louiz’ committed
191
192
void Bridge::send_channel_message(const Iid& iid, const std::string& body)
{
193
  if (iid.get_server().empty())
louiz’'s avatar
louiz’ committed
194
    {
195
      this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "",
196
197
198
199
                                    "cancel", "remote-server-not-found",
                                    std::to_string(iid) + " is not a valid channel name. "
                                    "A correct room jid is of the form: #<chan>%<server>",
                                    false);
louiz’'s avatar
louiz’ committed
200
201
      return;
    }
louiz’'s avatar
louiz’ committed
202
  IrcClient* irc = this->get_irc_client(iid.get_server());
203

204
205
206
207
208
209
210
211
212
213
214
215
  // Because an IRC message cannot contain \n, we need to convert each line
  // of text into a separate IRC message. For conveniance, we also cut the
  // message into submessages on the XMPP side, this way the user of the
  // gateway sees what was actually sent over IRC.  For example if an user
  // sends “hello\n/me waves”, two messages will be generated: “hello” and
  // “/me waves”. Note that the “command” handling (messages starting with
  // /me, /mode, etc) is done for each message generated this way. In the
  // above example, the /me will be interpreted.
  std::vector<std::string> lines = utils::split(body, '\n', true);
  if (lines.empty())
    return ;
  for (const std::string& line: lines)
louiz’'s avatar
louiz’ committed
216
    {
louiz’'s avatar
louiz’ committed
217
      if (line.substr(0, 5) == "/mode")
218
        {
louiz’'s avatar
louiz’ committed
219
          std::vector<std::string> args = utils::split(line.substr(5), ' ', false);
louiz’'s avatar
louiz’ committed
220
          irc->send_mode_command(iid.get_local(), args);
221
222
223
224
          continue;             // We do not want to send that back to the
                                // XMPP user, that’s not a textual message.
        }
      else if (line.substr(0, 4) == "/me ")
louiz’'s avatar
louiz’ committed
225
        irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01");
226
      else
louiz’'s avatar
louiz’ committed
227
        irc->send_channel_message(iid.get_local(), line);
228
      this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(),
229
                                   this->make_xmpp_body(line), this->user_jid);
louiz’'s avatar
louiz’ committed
230
    }
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& nick,
                                             const std::string& affiliation,
                                             const std::string& role)
{
  IrcClient* irc = this->get_irc_client(iid.get_server());
  IrcChannel* chan = irc->get_channel(iid.get_local());
  if (!chan || !chan->joined)
    return;
  IrcUser* user = chan->find_user(nick);
  if (!user)
    return;
  // For each affiliation or role, we have a “maximal” mode that we want to
  // set. We must remove any superior mode at the same time. For example if
  // the user already has +o mode, and we set its affiliation to member, we
  // remove the +o mode, and add +v.  For each “superior” mode (for example,
  // for +v, the superior modes are 'h', 'a', 'o' and 'q') we check if that
  // user has it, and if yes we remove that mode

  std::size_t nb = 1;               // the number of times the nick must be
                                    // repeated in the argument list
  std::string modes;                // The string of modes to
                                    // add/remove. For example "+v-aoh"
  std::vector<char> modes_to_remove; // List of modes to check for removal
  if (affiliation == "none")
    {
      modes = "";
      nb = 0;
      modes_to_remove = {'v', 'h', 'o', 'a', 'q'};
    }
  else if (affiliation == "member")
    {
      modes = "+v";
      modes_to_remove = {'h', 'o', 'a', 'q'};
    }
  else if (role == "moderator")
    {
      modes = "+h";
      modes_to_remove = {'o', 'a', 'q'};
    }
  else if (affiliation == "admin")
    {
      modes = "+o";
      modes_to_remove = {'a', 'q'};
    }
  else if (affiliation == "owner")
    {
      modes = "+a";
      modes_to_remove = {'q'};
    }
  else
    return;
  for (const char mode: modes_to_remove)
    if (user->modes.find(mode) != user->modes.end())
      {
        modes += "-"s + mode;
        nb++;
      }
  if (modes.empty())
    return;
  std::vector<std::string> args(nb, nick);
  args.insert(args.begin(), modes);
  irc->send_mode_command(iid.get_local(), args);
}

louiz’'s avatar
louiz’ committed
297
void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type)
louiz’'s avatar
louiz’ committed
298
{
louiz’'s avatar
louiz’ committed
299
  if (iid.get_local().empty() || iid.get_server().empty())
300
    {
301
      this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "",
302
303
304
305
                                    "cancel", "remote-server-not-found",
                                    std::to_string(iid) + " is not a valid channel name. "
                                    "A correct room jid is of the form: #<chan>%<server>",
                                    false);
306
307
      return;
    }
308
  IrcClient* irc = this->get_irc_client(iid.get_server());
309
310
311
312
313
314
  std::vector<std::string> lines = utils::split(body, '\n', true);
  if (lines.empty())
    return ;
  for (const std::string& line: lines)
    {
      if (line.substr(0, 4) == "/me ")
louiz’'s avatar
louiz’ committed
315
        irc->send_private_message(iid.get_local(), action_prefix + line.substr(4) + "\01", type);
316
      else
louiz’'s avatar
louiz’ committed
317
        irc->send_private_message(iid.get_local(), line, type);
318
    }
louiz’'s avatar
louiz’ committed
319
320
}

louiz’'s avatar
louiz’ committed
321
322
323
324
325
326
void Bridge::send_raw_message(const std::string& hostname, const std::string& body)
{
  IrcClient* irc = this->get_irc_client(hostname);
  irc->send_raw(body);
}

louiz’'s avatar
louiz’ committed
327
328
void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message)
{
louiz’'s avatar
louiz’ committed
329
  IrcClient* irc = this->get_irc_client(iid.get_server());
330
  irc->send_part_command(iid.get_local(), status_message);
louiz’'s avatar
louiz’ committed
331
332
}

louiz’'s avatar
louiz’ committed
333
334
void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick)
{
louiz’'s avatar
louiz’ committed
335
  IrcClient* irc = this->get_irc_client(iid.get_server());
336
  irc->send_nick_command(new_nick);
louiz’'s avatar
louiz’ committed
337
338
}

339
340
341
342
343
344
void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id,
                                           const std::string& to_jid)
{
  IrcClient* irc = this->get_irc_client(iid.get_server());

  irc->send_list_command();
345

346
347
348
349
350
351
352
  irc_responder_callback_t cb = [this, iid, iq_id, to_jid](const std::string& irc_hostname,
                                                           const IrcMessage& message) -> bool
    {
      static std::vector<ListElement> list;

      if (irc_hostname != iid.get_server())
        return false;
353
354
355
356
357
358
      if (message.command == "263" || message.command == "RPL_TRYAGAIN" ||
          message.command == "ERR_TOOMANYMATCHES" || message.command == "ERR_NOSUCHSERVER")
        {
          std::string text;
          if (message.arguments.size() >= 2)
            text = message.arguments[1];
359
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id,
360
                                        "wait", "service-unavailable", text, false);
361
362
363
364
365
366
367
368
369
370
371
          return true;
        }
      else if (message.command == "322" || message.command == "RPL_LIST")
        { // Add element to list
          if (message.arguments.size() == 4)
            list.emplace_back(message.arguments[1], message.arguments[2],
                              message.arguments[3]);
          return false;
        }
      else if (message.command == "323" || message.command == "RPL_LISTEND")
        { // Send the iq response with the content of the list
372
          this->xmpp.send_iq_room_list_result(iq_id, to_jid, std::to_string(iid), list);
373
374
375
376
377
378
379
          return true;
        }
      return false;
    };
  this->add_waiting_irc(std::move(cb));
}

380
381
void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason,
                           const std::string& iq_id, const std::string& to_jid)
382
{
louiz’'s avatar
louiz’ committed
383
  IrcClient* irc = this->get_irc_client(iid.get_server());
384
385
386
387

  irc->send_kick_command(iid.get_local(), target, reason);
  irc_responder_callback_t cb = [this, target, iq_id, to_jid, iid](const std::string& irc_hostname,
                                                                   const IrcMessage& message) -> bool
388
    {
389
390
391
392
393
394
395
      if (irc_hostname != iid.get_server())
        return false;
      if (message.command == "KICK" && message.arguments.size() >= 2)
        {
          const std::string target_later = message.arguments[1];
          const std::string chan_name_later = utils::tolower(message.arguments[0]);
          if (target_later != target || chan_name_later != iid.get_local())
396
            return false;
397
          this->xmpp.send_iq_result(iq_id, to_jid, std::to_string(iid));
398
399
400
401
402
403
404
405
406
        }
      else if (message.command == "401" && message.arguments.size() >= 2)
        {
          const std::string target_later = message.arguments[1];
          if (target_later != target)
            return false;
          std::string error_message = "No such nick";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
407
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found",
408
409
410
411
412
413
414
415
416
417
                                        error_message, false);
        }
      else if (message.command == "482" && message.arguments.size() >= 2)
        {
          const std::string chan_name_later = utils::tolower(message.arguments[1]);
          if (chan_name_later != iid.get_local())
            return false;
          std::string error_message = "You're not channel operator";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
418
          this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed",
419
420
421
422
423
                                        error_message, false);
        }
      return true;
    };
  this->add_waiting_irc(std::move(cb));
424
425
}

426
427
void Bridge::set_channel_topic(const Iid& iid, const std::string& subject)
{
louiz’'s avatar
louiz’ committed
428
  IrcClient* irc = this->get_irc_client(iid.get_server());
429
  irc->send_topic_command(iid.get_local(), subject);
430
431
}

louiz’'s avatar
louiz’ committed
432
433
434
435
436
437
438
void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os)
{
  std::string result(name + " " + version + " " + os);

  this->send_private_message(iid, "\01VERSION "s + result + "\01", "NOTICE");
}

439
440
void Bridge::send_irc_ping_result(const Iid& iid, const std::string& id)
{
441
  this->send_private_message(iid, "\01PING "s + utils::revstr(id) + "\01", "NOTICE");
442
443
444
445
446
447
448
449
450
}

void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const std::string& nick,
                                        const std::string& iq_id, const std::string& to_jid,
                                        const std::string& from_jid)
{
  Iid iid(nick + "!" + irc_hostname);
  this->send_private_message(iid, "\01PING " + iq_id + "\01");

451
  irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool
452
    {
louiz’'s avatar
louiz’ committed
453
      if (irc_hostname != hostname || message.arguments.size() < 2)
454
455
456
        return false;
      IrcUser user(message.prefix);
      const std::string body = message.arguments[1];
louiz’'s avatar
louiz’ committed
457
458
      if (message.command == "NOTICE" && utils::tolower(user.nick) == nick
          && body.substr(0, 6) == "\01PING ")
459
        {
460
          const std::string id = body.substr(6, body.size() - 7);
461
462
463
          if (id != iq_id)
            return false;
          Jid jid(from_jid);
464
          this->xmpp.send_iq_result(iq_id, to_jid, jid.local);
465
466
          return true;
        }
louiz’'s avatar
louiz’ committed
467
      if (message.command == "401" && message.arguments[1] == nick)
468
469
470
471
        {
          std::string error_message = "No such nick";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
472
          this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
473
474
475
476
477
478
479
480
481
                                        error_message, true);
          return true;
        }

      return false;
    };
  this->add_waiting_irc(std::move(cb));
}

482
483
484
485
486
487
488
489
void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string& nick,
                                               const std::string& iq_id, const std::string& to_jid,
                                               const std::string& from_jid)
{
  IrcClient* irc = this->get_irc_client(iid.get_server());
  IrcChannel* chan = irc->get_channel(iid.get_local());
  if (!chan->joined)
    {
490
      this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed",
491
492
493
494
495
                                    "", true);
      return;
    }
  if (chan->get_self()->nick != nick && !chan->find_user(nick))
    {
496
      this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
497
498
499
500
501
502
503
504
                                    "Recipient not in room", true);
      return;
    }

  // The user is in the room, send it a direct PING
  this->send_irc_user_ping_request(iid.get_server(), nick, iq_id, to_jid, from_jid);
}

505
506
507
508
void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid,
                             const std::string& from_jid)
{
  Jid jid(from_jid);
509
  if (irc_hostname.empty() || this->find_irc_client(irc_hostname))
510
    this->xmpp.send_iq_result(iq_id, to_jid, jid.local);
511
  else
512
    this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
513
514
515
                                  "", true);
}

516
517
518
519
520
521
522
523
void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target,
                                      const std::string& iq_id, const std::string& to_jid,
                                      const std::string& from_jid)
{
  Iid iid(target + "!" + irc_hostname);
  this->send_private_message(iid, "\01VERSION\01");
  // TODO, add a timer to remove that waiting iq if the server does not
  // respond with a matching command before n seconds
524
525
  irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool
    {
526
527
528
529
530
531
532
533
      if (irc_hostname != hostname)
        return false;
      IrcUser user(message.prefix);
      if (message.command == "NOTICE" && user.nick == target &&
          message.arguments.size() >= 2 && message.arguments[1].substr(0, 9) == "\01VERSION ")
        {
          // remove the "\01VERSION " and the "\01" parts from the string
          const std::string version = message.arguments[1].substr(9, message.arguments[1].size() - 10);
534
          this->xmpp.send_version(iq_id, to_jid, from_jid, version);
535
536
537
538
539
540
541
542
          return true;
        }
      if (message.command == "401" && message.arguments.size() >= 2
          && message.arguments[1] == target)
        {
          std::string error_message = "No such nick";
          if (message.arguments.size() >= 3)
            error_message = message.arguments[2];
543
          this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
544
545
546
547
                                        error_message, true);
          return true;
        }
      return false;
548
549
    };
  this->add_waiting_irc(std::move(cb));
550
551
}

louiz’'s avatar
louiz’ committed
552
void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc)
louiz’'s avatar
louiz’ committed
553
{
554
  const auto encoding = in_encoding_for(*this, iid);
louiz’'s avatar
louiz’ committed
555
  if (muc)
556
    this->xmpp.send_muc_message(std::to_string(iid), nick,
557
                                 this->make_xmpp_body(body, encoding), this->user_jid);
louiz’'s avatar
louiz’ committed
558
  else
559
560
561
562
563
564
565
566
567
    {
      std::string target = std::to_string(iid);
      bool fulljid = false;
      auto it = this->preferred_user_from.find(iid.get_local());
      if (it != this->preferred_user_from.end())
        {
          target = it->second;
          fulljid = true;
        }
568
      this->xmpp.send_message(target, this->make_xmpp_body(body, encoding),
569
570
                               this->user_jid, "chat", fulljid);
    }
louiz’'s avatar
louiz’ committed
571
572
}

573
574
575
void Bridge::send_presence_error(const Iid& iid, const std::string& nick,
                                 const std::string& type, const std::string& condition,
                                 const std::string& error_code, const std::string& text)
576
{
577
  this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text);
578
579
}

louiz’'s avatar
louiz’ committed
580
void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self)
louiz’'s avatar
louiz’ committed
581
{
582
  this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid, self);
583
  IrcClient* irc = this->find_irc_client(iid.get_server());
584
585
  if (irc && irc->number_of_joined_channels() == 0)
    irc->send_quit_command("");
louiz’'s avatar
louiz’ committed
586
587
}

588
589
590
591
592
void Bridge::send_nick_change(Iid&& iid,
                              const std::string& old_nick,
                              const std::string& new_nick,
                              const char user_mode,
                              const bool self)
louiz’'s avatar
louiz’ committed
593
{
594
595
596
597
  std::string affiliation;
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

598
  this->xmpp.send_nick_change(std::to_string(iid),
599
                               old_nick, new_nick, affiliation, role, this->user_jid, self);
louiz’'s avatar
louiz’ committed
600
601
}

602
603
void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg)
{
louiz’'s avatar
louiz’ committed
604
605
  std::string body;
  if (!author.empty())
606
607
    {
      IrcUser user(author);
louiz’'s avatar
louiz’ committed
608
      body = "\u000303"s + user.nick + (user.host.empty()?
louiz’'s avatar
louiz’ committed
609
610
                                        "\u0003: ":
                                        (" (\u000310" + user.host + "\u000303)\u0003: ")) + msg;
611
    }
louiz’'s avatar
louiz’ committed
612
613
  else
    body = msg;
614
615
616

  const auto encoding = in_encoding_for(*this, {from});
  this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid, "chat");
617
618
}

619
620
void Bridge::send_user_join(const std::string& hostname,
                            const std::string& chan_name,
621
                            const IrcUser* user,
622
                            const char user_mode,
623
                            const bool self)
624
{
625
626
627
628
  std::string affiliation;
  std::string role;
  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);

629
  this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host,
630
                             affiliation, role, this->user_jid, self);
631
632
}

633
void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who)
634
{
635
  const auto encoding = in_encoding_for(*this, {chan_name + '%' + hostname});
636
637
  this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server(
      "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid, who);
638
}
louiz’'s avatar
louiz’ committed
639
640
641

std::string Bridge::get_own_nick(const Iid& iid)
{
642
  IrcClient* irc = this->find_irc_client(iid.get_server());
louiz’'s avatar
louiz’ committed
643
644
645
646
  if (irc)
    return irc->get_own_nick();
  return "";
}
647

648
size_t Bridge::active_clients() const
louiz’'s avatar
louiz’ committed
649
650
651
652
{
  return this->irc_clients.size();
}

653
654
void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author)
{
655
  this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid);
656
}
657
658
659

void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname)
{
660
  this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid, "cancel", "conflict", "409", "");
661
}
662
663
664
665
666

void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode)
{
  std::string role;
  std::string affiliation;
667
668

  std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode);
669
  this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid);
670
}
671
672
673

void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname)
{
674
  this->xmpp.send_iq_version_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid);
675
}
676

677
678
679
680
681
682
void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname,
                                    const std::string& id)
{
  // Use revstr because the forwarded ping to target XMPP user must not be
  // the same that the request iq, but we also need to get it back easily
  // (revstr again)
683
  this->xmpp.send_ping_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid, utils::revstr(id));
684
685
}

686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid)
{
  auto it = this->preferred_user_from.find(nick);
  if (it == this->preferred_user_from.end())
    this->preferred_user_from.emplace(nick, full_jid);
  else
    this->preferred_user_from[nick] = full_jid;
}

void Bridge::remove_preferred_from_jid(const std::string& nick)
{
  auto it = this->preferred_user_from.find(nick);
  if (it != this->preferred_user_from.end())
    this->preferred_user_from.erase(it);
}
701

702
void Bridge::add_waiting_irc(irc_responder_callback_t&& callback)
703
{
704
  this->waiting_irc.emplace_back(std::move(callback));
705
706
}

707
void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message)
708
{
709
710
  auto it = this->waiting_irc.begin();
  while (it != this->waiting_irc.end())
711
712
    {
      if ((*it)(irc_hostname, message) == true)
713
        it = this->waiting_irc.erase(it);
714
715
716
717
      else
        ++it;
    }
}
718
719
720
721
722

std::unordered_map<std::string, std::shared_ptr<IrcClient>>& Bridge::get_irc_clients()
{
  return this->irc_clients;
}