IRCClient.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. #include "IRCClient.h"
  2. #include "IRCAppWindow.h"
  3. #include "IRCChannel.h"
  4. #include "IRCLogBuffer.h"
  5. #include "IRCQuery.h"
  6. #include "IRCWindow.h"
  7. #include "IRCWindowListModel.h"
  8. #include <AK/StringBuilder.h>
  9. #include <LibCore/CNotifier.h>
  10. #include <arpa/inet.h>
  11. #include <netinet/in.h>
  12. #include <stdio.h>
  13. #include <sys/socket.h>
  14. #include <time.h>
  15. #include <unistd.h>
  16. #define IRC_DEBUG
  17. enum IRCNumeric {
  18. RPL_WHOISUSER = 311,
  19. RPL_WHOISSERVER = 312,
  20. RPL_WHOISOPERATOR = 313,
  21. RPL_WHOISIDLE = 317,
  22. RPL_ENDOFWHOIS = 318,
  23. RPL_WHOISCHANNELS = 319,
  24. RPL_TOPIC = 332,
  25. RPL_TOPICWHOTIME = 333,
  26. RPL_NAMREPLY = 353,
  27. RPL_ENDOFNAMES = 366,
  28. };
  29. IRCClient::IRCClient()
  30. : m_nickname("seren1ty")
  31. , m_client_window_list_model(IRCWindowListModel::create(*this))
  32. , m_log(IRCLogBuffer::create())
  33. , m_config(CConfigFile::get_for_app("IRCClient"))
  34. {
  35. m_socket = new CTCPSocket(this);
  36. m_nickname = m_config->read_entry("User", "Nickname", "seren1ty");
  37. m_hostname = m_config->read_entry("Connection", "Server", "");
  38. m_port = m_config->read_num_entry("Connection", "Port", 6667);
  39. }
  40. IRCClient::~IRCClient()
  41. {
  42. }
  43. void IRCClient::set_server(const String& hostname, int port)
  44. {
  45. m_hostname = hostname;
  46. m_port = port;
  47. m_config->write_entry("Connection", "Server", hostname);
  48. m_config->write_num_entry("Connection", "Port", port);
  49. m_config->sync();
  50. }
  51. void IRCClient::on_socket_connected()
  52. {
  53. m_notifier = make<CNotifier>(m_socket->fd(), CNotifier::Read);
  54. m_notifier->on_ready_to_read = [this] { receive_from_server(); };
  55. send_user();
  56. send_nick();
  57. auto channel_str = m_config->read_entry("Connection", "AutoJoinChannels", "#test");
  58. dbgprintf("IRCClient: Channels to autojoin: %s\n", channel_str.characters());
  59. auto channels = channel_str.split(',');
  60. for (auto& channel : channels) {
  61. join_channel(channel);
  62. dbgprintf("IRCClient: Auto joining channel: %s\n", channel.characters());
  63. }
  64. }
  65. bool IRCClient::connect()
  66. {
  67. if (m_socket->is_connected())
  68. ASSERT_NOT_REACHED();
  69. m_socket->on_connected = [this] { on_socket_connected(); };
  70. bool success = m_socket->connect(m_hostname, m_port);
  71. if (!success)
  72. return false;
  73. return true;
  74. }
  75. void IRCClient::receive_from_server()
  76. {
  77. while (m_socket->can_read_line()) {
  78. auto line = m_socket->read_line(PAGE_SIZE);
  79. if (line.is_null()) {
  80. if (!m_socket->is_connected()) {
  81. printf("IRCClient: Connection closed!\n");
  82. exit(1);
  83. }
  84. ASSERT_NOT_REACHED();
  85. }
  86. process_line(move(line));
  87. }
  88. }
  89. void IRCClient::process_line(ByteBuffer&& line)
  90. {
  91. Message msg;
  92. Vector<char, 32> prefix;
  93. Vector<char, 32> command;
  94. Vector<char, 256> current_parameter;
  95. enum {
  96. Start,
  97. InPrefix,
  98. InCommand,
  99. InStartOfParameter,
  100. InParameter,
  101. InTrailingParameter,
  102. } state
  103. = Start;
  104. for (int i = 0; i < line.size(); ++i) {
  105. char ch = line[i];
  106. if (ch == '\r')
  107. continue;
  108. if (ch == '\n')
  109. break;
  110. switch (state) {
  111. case Start:
  112. if (ch == ':') {
  113. state = InPrefix;
  114. continue;
  115. }
  116. state = InCommand;
  117. [[fallthrough]];
  118. case InCommand:
  119. if (ch == ' ') {
  120. state = InStartOfParameter;
  121. continue;
  122. }
  123. command.append(ch);
  124. continue;
  125. case InPrefix:
  126. if (ch == ' ') {
  127. state = InCommand;
  128. continue;
  129. }
  130. prefix.append(ch);
  131. continue;
  132. case InStartOfParameter:
  133. if (ch == ':') {
  134. state = InTrailingParameter;
  135. continue;
  136. }
  137. state = InParameter;
  138. [[fallthrough]];
  139. case InParameter:
  140. if (ch == ' ') {
  141. if (!current_parameter.is_empty())
  142. msg.arguments.append(String(current_parameter.data(), current_parameter.size()));
  143. current_parameter.clear_with_capacity();
  144. state = InStartOfParameter;
  145. continue;
  146. }
  147. current_parameter.append(ch);
  148. continue;
  149. case InTrailingParameter:
  150. current_parameter.append(ch);
  151. continue;
  152. }
  153. }
  154. if (!current_parameter.is_empty())
  155. msg.arguments.append(String::copy(current_parameter));
  156. msg.prefix = String::copy(prefix);
  157. msg.command = String::copy(command);
  158. handle(msg);
  159. }
  160. void IRCClient::send(const String& text)
  161. {
  162. if (!m_socket->send(ByteBuffer::wrap(text.characters(), text.length()))) {
  163. perror("send");
  164. exit(1);
  165. }
  166. }
  167. void IRCClient::send_user()
  168. {
  169. send(String::format("USER %s 0 * :%s\r\n", m_nickname.characters(), m_nickname.characters()));
  170. }
  171. void IRCClient::send_nick()
  172. {
  173. send(String::format("NICK %s\r\n", m_nickname.characters()));
  174. }
  175. void IRCClient::send_pong(const String& server)
  176. {
  177. send(String::format("PONG %s\r\n", server.characters()));
  178. sleep(1);
  179. }
  180. void IRCClient::join_channel(const String& channel_name)
  181. {
  182. send(String::format("JOIN %s\r\n", channel_name.characters()));
  183. }
  184. void IRCClient::part_channel(const String& channel_name)
  185. {
  186. send(String::format("PART %s\r\n", channel_name.characters()));
  187. }
  188. void IRCClient::send_whois(const String& nick)
  189. {
  190. send(String::format("WHOIS %s\r\n", nick.characters()));
  191. }
  192. void IRCClient::handle(const Message& msg)
  193. {
  194. #ifdef IRC_DEBUG
  195. printf("IRCClient::execute: prefix='%s', command='%s', arguments=%d\n",
  196. msg.prefix.characters(),
  197. msg.command.characters(),
  198. msg.arguments.size());
  199. int i = 0;
  200. for (auto& arg : msg.arguments) {
  201. printf(" [%d]: %s\n", i, arg.characters());
  202. ++i;
  203. }
  204. #endif
  205. bool is_numeric;
  206. int numeric = msg.command.to_uint(is_numeric);
  207. if (is_numeric) {
  208. switch (numeric) {
  209. case RPL_WHOISCHANNELS:
  210. return handle_rpl_whoischannels(msg);
  211. case RPL_ENDOFWHOIS:
  212. return handle_rpl_endofwhois(msg);
  213. case RPL_WHOISOPERATOR:
  214. return handle_rpl_whoisoperator(msg);
  215. case RPL_WHOISSERVER:
  216. return handle_rpl_whoisserver(msg);
  217. case RPL_WHOISUSER:
  218. return handle_rpl_whoisuser(msg);
  219. case RPL_WHOISIDLE:
  220. return handle_rpl_whoisidle(msg);
  221. case RPL_TOPICWHOTIME:
  222. return handle_rpl_topicwhotime(msg);
  223. case RPL_TOPIC:
  224. return handle_rpl_topic(msg);
  225. case RPL_NAMREPLY:
  226. return handle_rpl_namreply(msg);
  227. case RPL_ENDOFNAMES:
  228. return handle_rpl_endofnames(msg);
  229. }
  230. }
  231. if (msg.command == "PING")
  232. return handle_ping(msg);
  233. if (msg.command == "JOIN")
  234. return handle_join(msg);
  235. if (msg.command == "PART")
  236. return handle_part(msg);
  237. if (msg.command == "TOPIC")
  238. return handle_topic(msg);
  239. if (msg.command == "PRIVMSG")
  240. return handle_privmsg_or_notice(msg, PrivmsgOrNotice::Privmsg);
  241. if (msg.command == "NOTICE")
  242. return handle_privmsg_or_notice(msg, PrivmsgOrNotice::Notice);
  243. if (msg.command == "NICK")
  244. return handle_nick(msg);
  245. if (msg.arguments.size() >= 2)
  246. add_server_message(String::format("[%s] %s", msg.command.characters(), msg.arguments[1].characters()));
  247. }
  248. void IRCClient::add_server_message(const String& text, Color color)
  249. {
  250. m_log->add_message(0, "", text, color);
  251. m_server_subwindow->did_add_message();
  252. }
  253. void IRCClient::send_privmsg(const String& target, const String& text)
  254. {
  255. send(String::format("PRIVMSG %s :%s\r\n", target.characters(), text.characters()));
  256. }
  257. void IRCClient::send_notice(const String& target, const String& text)
  258. {
  259. send(String::format("NOTICE %s :%s\r\n", target.characters(), text.characters()));
  260. }
  261. void IRCClient::handle_user_input_in_channel(const String& channel_name, const String& input)
  262. {
  263. if (input.is_empty())
  264. return;
  265. if (input[0] == '/')
  266. return handle_user_command(input);
  267. ensure_channel(channel_name).say(input);
  268. }
  269. void IRCClient::handle_user_input_in_query(const String& query_name, const String& input)
  270. {
  271. if (input.is_empty())
  272. return;
  273. if (input[0] == '/')
  274. return handle_user_command(input);
  275. ensure_query(query_name).say(input);
  276. }
  277. void IRCClient::handle_user_input_in_server(const String& input)
  278. {
  279. if (input.is_empty())
  280. return;
  281. if (input[0] == '/')
  282. return handle_user_command(input);
  283. }
  284. bool IRCClient::is_nick_prefix(char ch) const
  285. {
  286. switch (ch) {
  287. case '@':
  288. case '+':
  289. case '~':
  290. case '&':
  291. case '%':
  292. return true;
  293. }
  294. return false;
  295. }
  296. static bool has_ctcp_payload(const StringView& string)
  297. {
  298. return string.length() >= 2 && string[0] == 0x01 && string[string.length() - 1] == 0x01;
  299. }
  300. void IRCClient::handle_privmsg_or_notice(const Message& msg, PrivmsgOrNotice type)
  301. {
  302. if (msg.arguments.size() < 2)
  303. return;
  304. if (msg.prefix.is_empty())
  305. return;
  306. auto parts = msg.prefix.split('!');
  307. auto sender_nick = parts[0];
  308. auto target = msg.arguments[0];
  309. bool is_ctcp = has_ctcp_payload(msg.arguments[1]);
  310. #ifdef IRC_DEBUG
  311. printf("handle_privmsg_or_notice: type='%s'%s, sender_nick='%s', target='%s'\n",
  312. type == PrivmsgOrNotice::Privmsg ? "privmsg" : "notice",
  313. is_ctcp ? " (ctcp)" : "",
  314. sender_nick.characters(),
  315. target.characters());
  316. #endif
  317. if (sender_nick.is_empty())
  318. return;
  319. char sender_prefix = 0;
  320. if (is_nick_prefix(sender_nick[0])) {
  321. sender_prefix = sender_nick[0];
  322. sender_nick = sender_nick.substring(1, sender_nick.length() - 1);
  323. }
  324. String message_text = msg.arguments[1];
  325. auto message_color = Color::Black;
  326. if (is_ctcp) {
  327. auto ctcp_payload = msg.arguments[1].substring_view(1, msg.arguments[1].length() - 2);
  328. if (type == PrivmsgOrNotice::Privmsg)
  329. handle_ctcp_request(sender_nick, ctcp_payload);
  330. else
  331. handle_ctcp_response(sender_nick, ctcp_payload);
  332. StringBuilder builder;
  333. builder.append("(CTCP) ");
  334. builder.append(ctcp_payload);
  335. message_text = builder.to_string();
  336. message_color = Color::Blue;
  337. }
  338. {
  339. auto it = m_channels.find(target);
  340. if (it != m_channels.end()) {
  341. (*it).value->add_message(sender_prefix, sender_nick, message_text, message_color);
  342. return;
  343. }
  344. }
  345. // For NOTICE or CTCP messages, only put them in query if one already exists.
  346. // Otherwise, put them in the server window. This seems to match other clients.
  347. IRCQuery* query = nullptr;
  348. if (is_ctcp || type == PrivmsgOrNotice::Notice) {
  349. query = query_with_name(sender_nick);
  350. } else {
  351. query = &ensure_query(sender_nick);
  352. }
  353. if (query)
  354. query->add_message(sender_prefix, sender_nick, message_text, message_color);
  355. else {
  356. add_server_message(String::format("<%s> %s", sender_nick.characters(), message_text.characters()), message_color);
  357. }
  358. }
  359. IRCQuery* IRCClient::query_with_name(const String& name)
  360. {
  361. return m_queries.get(name).value_or(nullptr);
  362. }
  363. IRCQuery& IRCClient::ensure_query(const String& name)
  364. {
  365. auto it = m_queries.find(name);
  366. if (it != m_queries.end())
  367. return *(*it).value;
  368. auto query = IRCQuery::create(*this, name);
  369. auto& query_reference = *query;
  370. m_queries.set(name, query);
  371. return query_reference;
  372. }
  373. IRCChannel& IRCClient::ensure_channel(const String& name)
  374. {
  375. auto it = m_channels.find(name);
  376. if (it != m_channels.end())
  377. return *(*it).value;
  378. auto channel = IRCChannel::create(*this, name);
  379. auto& channel_reference = *channel;
  380. m_channels.set(name, channel);
  381. return channel_reference;
  382. }
  383. void IRCClient::handle_ping(const Message& msg)
  384. {
  385. if (msg.arguments.size() < 0)
  386. return;
  387. m_log->add_message(0, "", "Ping? Pong!");
  388. send_pong(msg.arguments[0]);
  389. }
  390. void IRCClient::handle_join(const Message& msg)
  391. {
  392. if (msg.arguments.size() != 1)
  393. return;
  394. auto prefix_parts = msg.prefix.split('!');
  395. if (prefix_parts.size() < 1)
  396. return;
  397. auto nick = prefix_parts[0];
  398. auto& channel_name = msg.arguments[0];
  399. ensure_channel(channel_name).handle_join(nick, msg.prefix);
  400. }
  401. void IRCClient::handle_part(const Message& msg)
  402. {
  403. if (msg.arguments.size() < 1)
  404. return;
  405. auto prefix_parts = msg.prefix.split('!');
  406. if (prefix_parts.size() < 1)
  407. return;
  408. auto nick = prefix_parts[0];
  409. auto& channel_name = msg.arguments[0];
  410. ensure_channel(channel_name).handle_part(nick, msg.prefix);
  411. }
  412. void IRCClient::handle_nick(const Message& msg)
  413. {
  414. auto prefix_parts = msg.prefix.split('!');
  415. if (prefix_parts.size() < 1)
  416. return;
  417. auto old_nick = prefix_parts[0];
  418. if (msg.arguments.size() != 1)
  419. return;
  420. auto& new_nick = msg.arguments[0];
  421. if (old_nick == m_nickname)
  422. m_nickname = new_nick;
  423. add_server_message(String::format("~ %s changed nickname to %s", old_nick.characters(), new_nick.characters()));
  424. if (on_nickname_changed)
  425. on_nickname_changed(new_nick);
  426. for (auto& it : m_channels) {
  427. it.value->notify_nick_changed(old_nick, new_nick);
  428. }
  429. }
  430. void IRCClient::handle_topic(const Message& msg)
  431. {
  432. if (msg.arguments.size() != 2)
  433. return;
  434. auto prefix_parts = msg.prefix.split('!');
  435. if (prefix_parts.size() < 1)
  436. return;
  437. auto nick = prefix_parts[0];
  438. auto& channel_name = msg.arguments[0];
  439. ensure_channel(channel_name).handle_topic(nick, msg.arguments[1]);
  440. }
  441. void IRCClient::handle_rpl_topic(const Message& msg)
  442. {
  443. if (msg.arguments.size() < 3)
  444. return;
  445. auto& channel_name = msg.arguments[1];
  446. auto& topic = msg.arguments[2];
  447. ensure_channel(channel_name).handle_topic({}, topic);
  448. // FIXME: Handle RPL_TOPICWHOTIME so we can know who set it and when.
  449. }
  450. void IRCClient::handle_rpl_namreply(const Message& msg)
  451. {
  452. if (msg.arguments.size() < 4)
  453. return;
  454. auto& channel_name = msg.arguments[2];
  455. auto& channel = ensure_channel(channel_name);
  456. auto members = msg.arguments[3].split(' ');
  457. for (auto& member : members) {
  458. if (member.is_empty())
  459. continue;
  460. char prefix = 0;
  461. if (is_nick_prefix(member[0]))
  462. prefix = member[0];
  463. channel.add_member(member, prefix);
  464. }
  465. }
  466. void IRCClient::handle_rpl_endofnames(const Message&)
  467. {
  468. }
  469. void IRCClient::handle_rpl_endofwhois(const Message&)
  470. {
  471. add_server_message("// End of WHOIS");
  472. }
  473. void IRCClient::handle_rpl_whoisoperator(const Message& msg)
  474. {
  475. if (msg.arguments.size() < 2)
  476. return;
  477. auto& nick = msg.arguments[1];
  478. add_server_message(String::format("* %s is an IRC operator", nick.characters()));
  479. }
  480. void IRCClient::handle_rpl_whoisserver(const Message& msg)
  481. {
  482. if (msg.arguments.size() < 3)
  483. return;
  484. auto& nick = msg.arguments[1];
  485. auto& server = msg.arguments[2];
  486. add_server_message(String::format("* %s is using server %s", nick.characters(), server.characters()));
  487. }
  488. void IRCClient::handle_rpl_whoisuser(const Message& msg)
  489. {
  490. if (msg.arguments.size() < 6)
  491. return;
  492. auto& nick = msg.arguments[1];
  493. auto& username = msg.arguments[2];
  494. auto& host = msg.arguments[3];
  495. auto& asterisk = msg.arguments[4];
  496. auto& realname = msg.arguments[5];
  497. (void)asterisk;
  498. add_server_message(String::format("* %s is %s@%s, real name: %s",
  499. nick.characters(),
  500. username.characters(),
  501. host.characters(),
  502. realname.characters()));
  503. }
  504. void IRCClient::handle_rpl_whoisidle(const Message& msg)
  505. {
  506. if (msg.arguments.size() < 3)
  507. return;
  508. auto& nick = msg.arguments[1];
  509. auto& secs = msg.arguments[2];
  510. add_server_message(String::format("* %s is %s seconds idle", nick.characters(), secs.characters()));
  511. }
  512. void IRCClient::handle_rpl_whoischannels(const Message& msg)
  513. {
  514. if (msg.arguments.size() < 3)
  515. return;
  516. auto& nick = msg.arguments[1];
  517. auto& channel_list = msg.arguments[2];
  518. add_server_message(String::format("* %s is in channels %s", nick.characters(), channel_list.characters()));
  519. }
  520. void IRCClient::handle_rpl_topicwhotime(const Message& msg)
  521. {
  522. if (msg.arguments.size() < 4)
  523. return;
  524. auto& channel_name = msg.arguments[1];
  525. auto& nick = msg.arguments[2];
  526. auto setat = msg.arguments[3];
  527. bool ok;
  528. time_t setat_time = setat.to_uint(ok);
  529. if (ok) {
  530. auto* tm = localtime(&setat_time);
  531. setat = String::format("%4u-%02u-%02u %02u:%02u:%02u",
  532. tm->tm_year + 1900,
  533. tm->tm_mon + 1,
  534. tm->tm_mday,
  535. tm->tm_hour,
  536. tm->tm_min,
  537. tm->tm_sec);
  538. }
  539. ensure_channel(channel_name).add_message(String::format("*** (set by %s at %s)", nick.characters(), setat.characters()), Color::Blue);
  540. }
  541. void IRCClient::register_subwindow(IRCWindow& subwindow)
  542. {
  543. if (subwindow.type() == IRCWindow::Server) {
  544. m_server_subwindow = &subwindow;
  545. subwindow.set_log_buffer(*m_log);
  546. }
  547. m_windows.append(&subwindow);
  548. m_client_window_list_model->update();
  549. }
  550. void IRCClient::unregister_subwindow(IRCWindow& subwindow)
  551. {
  552. if (subwindow.type() == IRCWindow::Server) {
  553. m_server_subwindow = &subwindow;
  554. }
  555. for (int i = 0; i < m_windows.size(); ++i) {
  556. if (m_windows.at(i) == &subwindow) {
  557. m_windows.remove(i);
  558. break;
  559. }
  560. }
  561. m_client_window_list_model->update();
  562. }
  563. void IRCClient::handle_user_command(const String& input)
  564. {
  565. auto parts = input.split_view(' ');
  566. if (parts.is_empty())
  567. return;
  568. auto command = String(parts[0]).to_uppercase();
  569. if (command == "/NICK") {
  570. if (parts.size() >= 2)
  571. change_nick(parts[1]);
  572. return;
  573. }
  574. if (command == "/JOIN") {
  575. if (parts.size() >= 2)
  576. join_channel(parts[1]);
  577. return;
  578. }
  579. if (command == "/PART") {
  580. if (parts.size() >= 2)
  581. part_channel(parts[1]);
  582. return;
  583. }
  584. if (command == "/QUERY") {
  585. if (parts.size() >= 2) {
  586. auto& query = ensure_query(parts[1]);
  587. IRCAppWindow::the().set_active_window(query.window());
  588. }
  589. return;
  590. }
  591. if (command == "/MSG") {
  592. if (parts.size() < 3)
  593. return;
  594. auto nick = parts[1];
  595. auto& query = ensure_query(nick);
  596. IRCAppWindow::the().set_active_window(query.window());
  597. query.say(input.view().substring_view_starting_after_substring(nick));
  598. return;
  599. }
  600. if (command == "/WHOIS") {
  601. if (parts.size() >= 2)
  602. send_whois(parts[1]);
  603. return;
  604. }
  605. }
  606. void IRCClient::change_nick(const String& nick)
  607. {
  608. send(String::format("NICK %s\r\n", nick.characters()));
  609. }
  610. void IRCClient::handle_whois_action(const String& nick)
  611. {
  612. send_whois(nick);
  613. }
  614. void IRCClient::handle_open_query_action(const String& nick)
  615. {
  616. ensure_query(nick);
  617. }
  618. void IRCClient::handle_change_nick_action(const String& nick)
  619. {
  620. change_nick(nick);
  621. }
  622. void IRCClient::handle_close_query_action(const String& nick)
  623. {
  624. m_queries.remove(nick);
  625. m_client_window_list_model->update();
  626. }
  627. void IRCClient::handle_join_action(const String& channel)
  628. {
  629. join_channel(channel);
  630. }
  631. void IRCClient::handle_part_action(const String& channel)
  632. {
  633. part_channel(channel);
  634. }
  635. void IRCClient::did_part_from_channel(Badge<IRCChannel>, IRCChannel& channel)
  636. {
  637. if (on_part_from_channel)
  638. on_part_from_channel(channel);
  639. }
  640. void IRCClient::send_ctcp_response(const StringView& peer, const StringView& payload)
  641. {
  642. StringBuilder builder;
  643. builder.append(0x01);
  644. builder.append(payload);
  645. builder.append(0x01);
  646. auto message = builder.to_string();
  647. send_notice(peer, message);
  648. }
  649. void IRCClient::handle_ctcp_request(const StringView& peer, const StringView& payload)
  650. {
  651. dbg() << "handle_ctcp_request: " << payload;
  652. if (payload == "VERSION") {
  653. send_ctcp_response(peer, "VERSION IRC Client [x86] / Serenity OS");
  654. return;
  655. }
  656. if (payload.starts_with("PING")) {
  657. send_ctcp_response(peer, payload);
  658. return;
  659. }
  660. }
  661. void IRCClient::handle_ctcp_response(const StringView& peer, const StringView& payload)
  662. {
  663. dbg() << "handle_ctcp_response(" << peer << "): " << payload;
  664. }