IRCClient.cpp 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include "IRCClient.h"
  27. #include "IRCAppWindow.h"
  28. #include "IRCChannel.h"
  29. #include "IRCLogBuffer.h"
  30. #include "IRCQuery.h"
  31. #include "IRCWindow.h"
  32. #include "IRCWindowListModel.h"
  33. #include <AK/QuickSort.h>
  34. #include <AK/StringBuilder.h>
  35. #include <LibCore/DateTime.h>
  36. #include <LibCore/Notifier.h>
  37. #include <arpa/inet.h>
  38. #include <netinet/in.h>
  39. #include <stdio.h>
  40. #include <sys/socket.h>
  41. #include <time.h>
  42. #include <unistd.h>
  43. #define IRC_DEBUG
  44. enum IRCNumeric {
  45. RPL_WHOISUSER = 311,
  46. RPL_WHOISSERVER = 312,
  47. RPL_WHOISOPERATOR = 313,
  48. RPL_ENDOFWHO = 315,
  49. RPL_WHOISIDLE = 317,
  50. RPL_ENDOFWHOIS = 318,
  51. RPL_WHOISCHANNELS = 319,
  52. RPL_TOPIC = 332,
  53. RPL_TOPICWHOTIME = 333,
  54. RPL_NAMREPLY = 353,
  55. RPL_ENDOFNAMES = 366,
  56. RPL_BANLIST = 367,
  57. RPL_ENDOFBANLIST = 368,
  58. RPL_ENDOFWHOWAS = 369,
  59. RPL_ENDOFMOTD = 376,
  60. ERR_NOSUCHNICK = 401,
  61. ERR_UNKNOWNCOMMAND = 421,
  62. ERR_NICKNAMEINUSE = 433,
  63. };
  64. IRCClient::IRCClient()
  65. : m_nickname("seren1ty")
  66. , m_client_window_list_model(IRCWindowListModel::create(*this))
  67. , m_log(IRCLogBuffer::create())
  68. , m_config(Core::ConfigFile::get_for_app("IRCClient"))
  69. {
  70. m_socket = Core::TCPSocket::construct(this);
  71. m_nickname = m_config->read_entry("User", "Nickname", "seren1ty");
  72. m_hostname = m_config->read_entry("Connection", "Server", "");
  73. m_port = m_config->read_num_entry("Connection", "Port", 6667);
  74. m_ctcp_version_reply = m_config->read_entry("CTCP", "VersionReply", "IRC Client [x86] / Serenity OS");
  75. m_ctcp_userinfo_reply = m_config->read_entry("CTCP", "UserInfoReply", "anon");
  76. m_ctcp_finger_reply = m_config->read_entry("CTCP", "FingerReply", "anon");
  77. }
  78. IRCClient::~IRCClient()
  79. {
  80. }
  81. void IRCClient::set_server(const String& hostname, int port)
  82. {
  83. m_hostname = hostname;
  84. m_port = port;
  85. m_config->write_entry("Connection", "Server", hostname);
  86. m_config->write_num_entry("Connection", "Port", port);
  87. m_config->sync();
  88. }
  89. void IRCClient::on_socket_connected()
  90. {
  91. m_notifier = Core::Notifier::construct(m_socket->fd(), Core::Notifier::Read);
  92. m_notifier->on_ready_to_read = [this] { receive_from_server(); };
  93. send_user();
  94. send_nick();
  95. auto channel_str = m_config->read_entry("Connection", "AutoJoinChannels", "#test");
  96. dbgprintf("IRCClient: Channels to autojoin: %s\n", channel_str.characters());
  97. auto channels = channel_str.split(',');
  98. for (auto& channel : channels) {
  99. join_channel(channel);
  100. dbgprintf("IRCClient: Auto joining channel: %s\n", channel.characters());
  101. }
  102. }
  103. bool IRCClient::connect()
  104. {
  105. if (m_socket->is_connected())
  106. ASSERT_NOT_REACHED();
  107. m_socket->on_connected = [this] { on_socket_connected(); };
  108. bool success = m_socket->connect(m_hostname, m_port);
  109. if (!success)
  110. return false;
  111. return true;
  112. }
  113. void IRCClient::receive_from_server()
  114. {
  115. while (m_socket->can_read_line()) {
  116. auto line = m_socket->read_line(PAGE_SIZE);
  117. if (line.is_null()) {
  118. if (!m_socket->is_connected()) {
  119. printf("IRCClient: Connection closed!\n");
  120. exit(1);
  121. }
  122. ASSERT_NOT_REACHED();
  123. }
  124. process_line(move(line));
  125. }
  126. }
  127. void IRCClient::process_line(ByteBuffer&& line)
  128. {
  129. Message msg;
  130. Vector<char, 32> prefix;
  131. Vector<char, 32> command;
  132. Vector<char, 256> current_parameter;
  133. enum {
  134. Start,
  135. InPrefix,
  136. InCommand,
  137. InStartOfParameter,
  138. InParameter,
  139. InTrailingParameter,
  140. } state
  141. = Start;
  142. for (size_t i = 0; i < line.size(); ++i) {
  143. char ch = line[i];
  144. if (ch == '\r')
  145. continue;
  146. if (ch == '\n')
  147. break;
  148. switch (state) {
  149. case Start:
  150. if (ch == ':') {
  151. state = InPrefix;
  152. continue;
  153. }
  154. state = InCommand;
  155. [[fallthrough]];
  156. case InCommand:
  157. if (ch == ' ') {
  158. state = InStartOfParameter;
  159. continue;
  160. }
  161. command.append(ch);
  162. continue;
  163. case InPrefix:
  164. if (ch == ' ') {
  165. state = InCommand;
  166. continue;
  167. }
  168. prefix.append(ch);
  169. continue;
  170. case InStartOfParameter:
  171. if (ch == ':') {
  172. state = InTrailingParameter;
  173. continue;
  174. }
  175. state = InParameter;
  176. [[fallthrough]];
  177. case InParameter:
  178. if (ch == ' ') {
  179. if (!current_parameter.is_empty())
  180. msg.arguments.append(String(current_parameter.data(), current_parameter.size()));
  181. current_parameter.clear_with_capacity();
  182. state = InStartOfParameter;
  183. continue;
  184. }
  185. current_parameter.append(ch);
  186. continue;
  187. case InTrailingParameter:
  188. current_parameter.append(ch);
  189. continue;
  190. }
  191. }
  192. if (!current_parameter.is_empty())
  193. msg.arguments.append(String::copy(current_parameter));
  194. msg.prefix = String::copy(prefix);
  195. msg.command = String::copy(command);
  196. handle(msg);
  197. }
  198. void IRCClient::send(const String& text)
  199. {
  200. if (!m_socket->send(ByteBuffer::wrap(text.characters(), text.length()))) {
  201. perror("send");
  202. exit(1);
  203. }
  204. }
  205. void IRCClient::send_user()
  206. {
  207. send(String::format("USER %s 0 * :%s\r\n", m_nickname.characters(), m_nickname.characters()));
  208. }
  209. void IRCClient::send_nick()
  210. {
  211. send(String::format("NICK %s\r\n", m_nickname.characters()));
  212. }
  213. void IRCClient::send_pong(const String& server)
  214. {
  215. send(String::format("PONG %s\r\n", server.characters()));
  216. sleep(1);
  217. }
  218. void IRCClient::join_channel(const String& channel_name)
  219. {
  220. send(String::format("JOIN %s\r\n", channel_name.characters()));
  221. }
  222. void IRCClient::part_channel(const String& channel_name)
  223. {
  224. send(String::format("PART %s\r\n", channel_name.characters()));
  225. }
  226. void IRCClient::send_whois(const String& nick)
  227. {
  228. send(String::format("WHOIS %s\r\n", nick.characters()));
  229. }
  230. void IRCClient::handle(const Message& msg)
  231. {
  232. #ifdef IRC_DEBUG
  233. printf("IRCClient::execute: prefix='%s', command='%s', arguments=%zu\n",
  234. msg.prefix.characters(),
  235. msg.command.characters(),
  236. msg.arguments.size());
  237. int i = 0;
  238. for (auto& arg : msg.arguments) {
  239. printf(" [%d]: %s\n", i, arg.characters());
  240. ++i;
  241. }
  242. #endif
  243. bool is_numeric;
  244. int numeric = msg.command.to_uint(is_numeric);
  245. if (is_numeric) {
  246. switch (numeric) {
  247. case RPL_WHOISCHANNELS:
  248. return handle_rpl_whoischannels(msg);
  249. case RPL_ENDOFWHO:
  250. return handle_rpl_endofwho(msg);
  251. case RPL_ENDOFWHOIS:
  252. return handle_rpl_endofwhois(msg);
  253. case RPL_ENDOFWHOWAS:
  254. return handle_rpl_endofwhowas(msg);
  255. case RPL_ENDOFMOTD:
  256. return handle_rpl_endofmotd(msg);
  257. case RPL_WHOISOPERATOR:
  258. return handle_rpl_whoisoperator(msg);
  259. case RPL_WHOISSERVER:
  260. return handle_rpl_whoisserver(msg);
  261. case RPL_WHOISUSER:
  262. return handle_rpl_whoisuser(msg);
  263. case RPL_WHOISIDLE:
  264. return handle_rpl_whoisidle(msg);
  265. case RPL_TOPICWHOTIME:
  266. return handle_rpl_topicwhotime(msg);
  267. case RPL_TOPIC:
  268. return handle_rpl_topic(msg);
  269. case RPL_NAMREPLY:
  270. return handle_rpl_namreply(msg);
  271. case RPL_ENDOFNAMES:
  272. return handle_rpl_endofnames(msg);
  273. case RPL_BANLIST:
  274. return handle_rpl_banlist(msg);
  275. case RPL_ENDOFBANLIST:
  276. return handle_rpl_endofbanlist(msg);
  277. case ERR_NOSUCHNICK:
  278. return handle_err_nosuchnick(msg);
  279. case ERR_UNKNOWNCOMMAND:
  280. return handle_err_unknowncommand(msg);
  281. case ERR_NICKNAMEINUSE:
  282. return handle_err_nicknameinuse(msg);
  283. }
  284. }
  285. if (msg.command == "PING")
  286. return handle_ping(msg);
  287. if (msg.command == "JOIN")
  288. return handle_join(msg);
  289. if (msg.command == "PART")
  290. return handle_part(msg);
  291. if (msg.command == "QUIT")
  292. return handle_quit(msg);
  293. if (msg.command == "TOPIC")
  294. return handle_topic(msg);
  295. if (msg.command == "PRIVMSG")
  296. return handle_privmsg_or_notice(msg, PrivmsgOrNotice::Privmsg);
  297. if (msg.command == "NOTICE")
  298. return handle_privmsg_or_notice(msg, PrivmsgOrNotice::Notice);
  299. if (msg.command == "NICK")
  300. return handle_nick(msg);
  301. if (msg.arguments.size() >= 2)
  302. add_server_message(String::format("[%s] %s", msg.command.characters(), msg.arguments[1].characters()));
  303. }
  304. void IRCClient::add_server_message(const String& text, Color color)
  305. {
  306. m_log->add_message(0, "", text, color);
  307. m_server_subwindow->did_add_message();
  308. }
  309. void IRCClient::send_topic(const String& channel_name, const String& text)
  310. {
  311. send(String::format("TOPIC %s :%s\r\n", channel_name.characters(), text.characters()));
  312. }
  313. void IRCClient::send_invite(const String& channel_name, const String& nick)
  314. {
  315. send(String::format("INVITE %s %s\r\n", nick.characters(), channel_name.characters()));
  316. }
  317. void IRCClient::send_banlist(const String& channel_name)
  318. {
  319. send(String::format("MODE %s +b\r\n", channel_name.characters()));
  320. }
  321. void IRCClient::send_voice_user(const String& channel_name, const String& nick)
  322. {
  323. send(String::format("MODE %s +v %s\r\n", channel_name.characters(), nick.characters()));
  324. }
  325. void IRCClient::send_devoice_user(const String& channel_name, const String& nick)
  326. {
  327. send(String::format("MODE %s -v %s\r\n", channel_name.characters(), nick.characters()));
  328. }
  329. void IRCClient::send_op_user(const String& channel_name, const String& nick)
  330. {
  331. send(String::format("MODE %s +o %s\r\n", channel_name.characters(), nick.characters()));
  332. }
  333. void IRCClient::send_deop_user(const String& channel_name, const String& nick)
  334. {
  335. send(String::format("MODE %s -o %s\r\n", channel_name.characters(), nick.characters()));
  336. }
  337. void IRCClient::send_kick(const String& channel_name, const String& nick, const String& comment)
  338. {
  339. send(String::format("KICK %s %s :%s\r\n", channel_name.characters(), nick.characters(), comment.characters()));
  340. }
  341. void IRCClient::send_list()
  342. {
  343. send("LIST\r\n");
  344. }
  345. void IRCClient::send_privmsg(const String& target, const String& text)
  346. {
  347. send(String::format("PRIVMSG %s :%s\r\n", target.characters(), text.characters()));
  348. }
  349. void IRCClient::send_notice(const String& target, const String& text)
  350. {
  351. send(String::format("NOTICE %s :%s\r\n", target.characters(), text.characters()));
  352. }
  353. void IRCClient::handle_user_input_in_channel(const String& channel_name, const String& input)
  354. {
  355. if (input.is_empty())
  356. return;
  357. if (input[0] == '/')
  358. return handle_user_command(input);
  359. ensure_channel(channel_name).say(input);
  360. }
  361. void IRCClient::handle_user_input_in_query(const String& query_name, const String& input)
  362. {
  363. if (input.is_empty())
  364. return;
  365. if (input[0] == '/')
  366. return handle_user_command(input);
  367. ensure_query(query_name).say(input);
  368. }
  369. void IRCClient::handle_user_input_in_server(const String& input)
  370. {
  371. if (input.is_empty())
  372. return;
  373. if (input[0] == '/')
  374. return handle_user_command(input);
  375. }
  376. bool IRCClient::is_nick_prefix(char ch)
  377. {
  378. switch (ch) {
  379. case '@':
  380. case '+':
  381. case '~':
  382. case '&':
  383. case '%':
  384. return true;
  385. }
  386. return false;
  387. }
  388. bool IRCClient::is_channel_prefix(char ch)
  389. {
  390. switch (ch) {
  391. case '&':
  392. case '#':
  393. case '+':
  394. case '!':
  395. return true;
  396. }
  397. return false;
  398. }
  399. static bool has_ctcp_payload(const StringView& string)
  400. {
  401. return string.length() >= 2 && string[0] == 0x01 && string[string.length() - 1] == 0x01;
  402. }
  403. void IRCClient::handle_privmsg_or_notice(const Message& msg, PrivmsgOrNotice type)
  404. {
  405. if (msg.arguments.size() < 2)
  406. return;
  407. if (msg.prefix.is_empty())
  408. return;
  409. auto parts = msg.prefix.split('!');
  410. auto sender_nick = parts[0];
  411. auto target = msg.arguments[0];
  412. bool is_ctcp = has_ctcp_payload(msg.arguments[1]);
  413. #ifdef IRC_DEBUG
  414. printf("handle_privmsg_or_notice: type='%s'%s, sender_nick='%s', target='%s'\n",
  415. type == PrivmsgOrNotice::Privmsg ? "privmsg" : "notice",
  416. is_ctcp ? " (ctcp)" : "",
  417. sender_nick.characters(),
  418. target.characters());
  419. #endif
  420. if (sender_nick.is_empty())
  421. return;
  422. char sender_prefix = 0;
  423. if (is_nick_prefix(sender_nick[0])) {
  424. sender_prefix = sender_nick[0];
  425. sender_nick = sender_nick.substring(1, sender_nick.length() - 1);
  426. }
  427. String message_text = msg.arguments[1];
  428. auto message_color = Color::Black;
  429. if (is_ctcp) {
  430. auto ctcp_payload = msg.arguments[1].substring_view(1, msg.arguments[1].length() - 2);
  431. if (type == PrivmsgOrNotice::Privmsg)
  432. handle_ctcp_request(sender_nick, ctcp_payload);
  433. else
  434. handle_ctcp_response(sender_nick, ctcp_payload);
  435. StringBuilder builder;
  436. builder.append("(CTCP) ");
  437. builder.append(ctcp_payload);
  438. message_text = builder.to_string();
  439. message_color = Color::Blue;
  440. }
  441. {
  442. auto it = m_channels.find(target);
  443. if (it != m_channels.end()) {
  444. (*it).value->add_message(sender_prefix, sender_nick, message_text, message_color);
  445. return;
  446. }
  447. }
  448. // For NOTICE or CTCP messages, only put them in query if one already exists.
  449. // Otherwise, put them in the server window. This seems to match other clients.
  450. IRCQuery* query = nullptr;
  451. if (is_ctcp || type == PrivmsgOrNotice::Notice) {
  452. query = query_with_name(sender_nick);
  453. } else {
  454. query = &ensure_query(sender_nick);
  455. }
  456. if (query)
  457. query->add_message(sender_prefix, sender_nick, message_text, message_color);
  458. else {
  459. add_server_message(String::format("<%s> %s", sender_nick.characters(), message_text.characters()), message_color);
  460. }
  461. }
  462. IRCQuery* IRCClient::query_with_name(const String& name)
  463. {
  464. return const_cast<IRCQuery*>(m_queries.get(name).value_or(nullptr));
  465. }
  466. IRCQuery& IRCClient::ensure_query(const String& name)
  467. {
  468. auto it = m_queries.find(name);
  469. if (it != m_queries.end())
  470. return *(*it).value;
  471. auto query = IRCQuery::create(*this, name);
  472. auto& query_reference = *query;
  473. m_queries.set(name, query);
  474. return query_reference;
  475. }
  476. IRCChannel& IRCClient::ensure_channel(const String& name)
  477. {
  478. auto it = m_channels.find(name);
  479. if (it != m_channels.end())
  480. return *(*it).value;
  481. auto channel = IRCChannel::create(*this, name);
  482. auto& channel_reference = *channel;
  483. m_channels.set(name, channel);
  484. return channel_reference;
  485. }
  486. void IRCClient::handle_ping(const Message& msg)
  487. {
  488. if (msg.arguments.size() < 1)
  489. return;
  490. m_log->add_message(0, "", "Ping? Pong!");
  491. send_pong(msg.arguments[0]);
  492. }
  493. void IRCClient::handle_join(const Message& msg)
  494. {
  495. if (msg.arguments.size() != 1)
  496. return;
  497. auto prefix_parts = msg.prefix.split('!');
  498. if (prefix_parts.size() < 1)
  499. return;
  500. auto nick = prefix_parts[0];
  501. auto& channel_name = msg.arguments[0];
  502. ensure_channel(channel_name).handle_join(nick, msg.prefix);
  503. }
  504. void IRCClient::handle_part(const Message& msg)
  505. {
  506. if (msg.arguments.size() < 1)
  507. return;
  508. auto prefix_parts = msg.prefix.split('!');
  509. if (prefix_parts.size() < 1)
  510. return;
  511. auto nick = prefix_parts[0];
  512. auto& channel_name = msg.arguments[0];
  513. ensure_channel(channel_name).handle_part(nick, msg.prefix);
  514. }
  515. void IRCClient::handle_quit(const Message& msg)
  516. {
  517. if (msg.arguments.size() < 1)
  518. return;
  519. auto prefix_parts = msg.prefix.split('!');
  520. if (prefix_parts.size() < 1)
  521. return;
  522. auto nick = prefix_parts[0];
  523. auto& message = msg.arguments[0];
  524. for (auto& it : m_channels) {
  525. it.value->handle_quit(nick, msg.prefix, message);
  526. }
  527. }
  528. void IRCClient::handle_nick(const Message& msg)
  529. {
  530. auto prefix_parts = msg.prefix.split('!');
  531. if (prefix_parts.size() < 1)
  532. return;
  533. auto old_nick = prefix_parts[0];
  534. if (msg.arguments.size() != 1)
  535. return;
  536. auto& new_nick = msg.arguments[0];
  537. if (old_nick == m_nickname)
  538. m_nickname = new_nick;
  539. add_server_message(String::format("~ %s changed nickname to %s", old_nick.characters(), new_nick.characters()));
  540. if (on_nickname_changed)
  541. on_nickname_changed(new_nick);
  542. for (auto& it : m_channels) {
  543. it.value->notify_nick_changed(old_nick, new_nick);
  544. }
  545. }
  546. void IRCClient::handle_topic(const Message& msg)
  547. {
  548. if (msg.arguments.size() != 2)
  549. return;
  550. auto prefix_parts = msg.prefix.split('!');
  551. if (prefix_parts.size() < 1)
  552. return;
  553. auto nick = prefix_parts[0];
  554. auto& channel_name = msg.arguments[0];
  555. ensure_channel(channel_name).handle_topic(nick, msg.arguments[1]);
  556. }
  557. void IRCClient::handle_rpl_topic(const Message& msg)
  558. {
  559. if (msg.arguments.size() < 3)
  560. return;
  561. auto& channel_name = msg.arguments[1];
  562. auto& topic = msg.arguments[2];
  563. ensure_channel(channel_name).handle_topic({}, topic);
  564. // FIXME: Handle RPL_TOPICWHOTIME so we can know who set it and when.
  565. }
  566. void IRCClient::handle_rpl_namreply(const Message& msg)
  567. {
  568. if (msg.arguments.size() < 4)
  569. return;
  570. auto& channel_name = msg.arguments[2];
  571. auto& channel = ensure_channel(channel_name);
  572. auto members = msg.arguments[3].split(' ');
  573. quick_sort(members, [](auto& a, auto& b) {
  574. return strcasecmp(a.characters(), b.characters()) < 0;
  575. });
  576. for (auto& member : members) {
  577. if (member.is_empty())
  578. continue;
  579. char prefix = 0;
  580. if (is_nick_prefix(member[0]))
  581. prefix = member[0];
  582. channel.add_member(member, prefix);
  583. }
  584. }
  585. void IRCClient::handle_rpl_endofnames(const Message&)
  586. {
  587. add_server_message("// End of NAMES");
  588. }
  589. void IRCClient::handle_rpl_banlist(const Message& msg)
  590. {
  591. if (msg.arguments.size() < 5)
  592. return;
  593. auto& channel = msg.arguments[1];
  594. auto& mask = msg.arguments[2];
  595. auto& user = msg.arguments[3];
  596. auto& datestamp = msg.arguments[4];
  597. add_server_message(String::format("* %s: %s on %s by %s", channel.characters(), mask.characters(), datestamp.characters(), user.characters()));
  598. }
  599. void IRCClient::handle_rpl_endofbanlist(const Message&)
  600. {
  601. add_server_message("// End of BANLIST");
  602. }
  603. void IRCClient::handle_rpl_endofwho(const Message&)
  604. {
  605. add_server_message("// End of WHO");
  606. }
  607. void IRCClient::handle_rpl_endofwhois(const Message&)
  608. {
  609. add_server_message("// End of WHOIS");
  610. }
  611. void IRCClient::handle_rpl_endofwhowas(const Message&)
  612. {
  613. add_server_message("// End of WHOWAS");
  614. }
  615. void IRCClient::handle_rpl_endofmotd(const Message&)
  616. {
  617. add_server_message("// End of MOTD");
  618. }
  619. void IRCClient::handle_rpl_whoisoperator(const Message& msg)
  620. {
  621. if (msg.arguments.size() < 2)
  622. return;
  623. auto& nick = msg.arguments[1];
  624. add_server_message(String::format("* %s is an IRC operator", nick.characters()));
  625. }
  626. void IRCClient::handle_rpl_whoisserver(const Message& msg)
  627. {
  628. if (msg.arguments.size() < 3)
  629. return;
  630. auto& nick = msg.arguments[1];
  631. auto& server = msg.arguments[2];
  632. add_server_message(String::format("* %s is using server %s", nick.characters(), server.characters()));
  633. }
  634. void IRCClient::handle_rpl_whoisuser(const Message& msg)
  635. {
  636. if (msg.arguments.size() < 6)
  637. return;
  638. auto& nick = msg.arguments[1];
  639. auto& username = msg.arguments[2];
  640. auto& host = msg.arguments[3];
  641. auto& asterisk = msg.arguments[4];
  642. auto& realname = msg.arguments[5];
  643. (void)asterisk;
  644. add_server_message(String::format("* %s is %s@%s, real name: %s",
  645. nick.characters(),
  646. username.characters(),
  647. host.characters(),
  648. realname.characters()));
  649. }
  650. void IRCClient::handle_rpl_whoisidle(const Message& msg)
  651. {
  652. if (msg.arguments.size() < 3)
  653. return;
  654. auto& nick = msg.arguments[1];
  655. auto& secs = msg.arguments[2];
  656. add_server_message(String::format("* %s is %s seconds idle", nick.characters(), secs.characters()));
  657. }
  658. void IRCClient::handle_rpl_whoischannels(const Message& msg)
  659. {
  660. if (msg.arguments.size() < 3)
  661. return;
  662. auto& nick = msg.arguments[1];
  663. auto& channel_list = msg.arguments[2];
  664. add_server_message(String::format("* %s is in channels %s", nick.characters(), channel_list.characters()));
  665. }
  666. void IRCClient::handle_rpl_topicwhotime(const Message& msg)
  667. {
  668. if (msg.arguments.size() < 4)
  669. return;
  670. auto& channel_name = msg.arguments[1];
  671. auto& nick = msg.arguments[2];
  672. auto setat = msg.arguments[3];
  673. bool ok;
  674. time_t setat_time = setat.to_uint(ok);
  675. if (ok)
  676. setat = Core::DateTime::from_timestamp(setat_time).to_string();
  677. ensure_channel(channel_name).add_message(String::format("*** (set by %s at %s)", nick.characters(), setat.characters()), Color::Blue);
  678. }
  679. void IRCClient::handle_err_nosuchnick(const Message& msg)
  680. {
  681. if (msg.arguments.size() < 3)
  682. return;
  683. auto& nick = msg.arguments[1];
  684. auto& message = msg.arguments[2];
  685. add_server_message(String::format("* %s :%s", nick.characters(), message.characters()));
  686. }
  687. void IRCClient::handle_err_unknowncommand(const Message& msg)
  688. {
  689. if (msg.arguments.size() < 2)
  690. return;
  691. auto& cmd = msg.arguments[1];
  692. add_server_message(String::format("* Unknown command: %s", cmd.characters()));
  693. }
  694. void IRCClient::handle_err_nicknameinuse(const Message& msg)
  695. {
  696. if (msg.arguments.size() < 2)
  697. return;
  698. auto& nick = msg.arguments[1];
  699. add_server_message(String::format("* %s :Nickname in use", nick.characters()));
  700. }
  701. void IRCClient::register_subwindow(IRCWindow& subwindow)
  702. {
  703. if (subwindow.type() == IRCWindow::Server) {
  704. m_server_subwindow = &subwindow;
  705. subwindow.set_log_buffer(*m_log);
  706. }
  707. m_windows.append(&subwindow);
  708. m_client_window_list_model->update();
  709. }
  710. void IRCClient::unregister_subwindow(IRCWindow& subwindow)
  711. {
  712. if (subwindow.type() == IRCWindow::Server) {
  713. m_server_subwindow = &subwindow;
  714. }
  715. for (size_t i = 0; i < m_windows.size(); ++i) {
  716. if (m_windows.at(i) == &subwindow) {
  717. m_windows.remove(i);
  718. break;
  719. }
  720. }
  721. m_client_window_list_model->update();
  722. }
  723. void IRCClient::handle_user_command(const String& input)
  724. {
  725. auto parts = input.split_view(' ');
  726. if (parts.is_empty())
  727. return;
  728. auto command = String(parts[0]).to_uppercase();
  729. if (command == "/RAW") {
  730. if (parts.size() <= 1)
  731. return;
  732. int command_length = command.length() + 1;
  733. StringView raw_message = input.view().substring_view(command_length, input.view().length() - command_length);
  734. send(String::format("%s\r\n", String(raw_message).characters()));
  735. return;
  736. }
  737. if (command == "/NICK") {
  738. if (parts.size() >= 2)
  739. change_nick(parts[1]);
  740. return;
  741. }
  742. if (command == "/JOIN") {
  743. if (parts.size() >= 2)
  744. join_channel(parts[1]);
  745. return;
  746. }
  747. if (command == "/PART") {
  748. if (parts.size() >= 2) {
  749. auto channel = parts[1];
  750. part_channel(channel);
  751. } else {
  752. auto* window = current_window();
  753. if (!window || window->type() != IRCWindow::Type::Channel)
  754. return;
  755. auto channel = window->channel().name();
  756. join_channel(channel);
  757. }
  758. return;
  759. }
  760. if (command == "/HOP") {
  761. if (parts.size() >= 2) {
  762. auto channel = parts[1];
  763. part_channel(channel);
  764. join_channel(channel);
  765. } else {
  766. auto* window = current_window();
  767. if (!window || window->type() != IRCWindow::Type::Channel)
  768. return;
  769. auto channel = window->channel().name();
  770. part_channel(channel);
  771. join_channel(channel);
  772. }
  773. return;
  774. }
  775. if (command == "/BANLIST") {
  776. if (parts.size() >= 2) {
  777. auto channel = parts[1];
  778. send_banlist(channel);
  779. } else {
  780. auto* window = current_window();
  781. if (!window || window->type() != IRCWindow::Type::Channel)
  782. return;
  783. auto channel = window->channel().name();
  784. send_banlist(channel);
  785. }
  786. return;
  787. }
  788. if (command == "/TOPIC") {
  789. if (parts.size() < 2)
  790. return;
  791. if (parts[1].is_empty())
  792. return;
  793. if (is_channel_prefix(parts[1][0])) {
  794. if (parts.size() < 3)
  795. return;
  796. auto channel = parts[1];
  797. auto topic = input.view().substring_view_starting_after_substring(channel);
  798. send_topic(channel, topic);
  799. } else {
  800. auto* window = current_window();
  801. if (!window || window->type() != IRCWindow::Type::Channel)
  802. return;
  803. auto channel = window->channel().name();
  804. auto topic = input.view().substring_view_starting_after_substring(parts[0]);
  805. send_topic(channel, topic);
  806. }
  807. return;
  808. }
  809. if (command == "/KICK") {
  810. if (parts.size() < 2)
  811. return;
  812. if (parts[1].is_empty())
  813. return;
  814. if (is_channel_prefix(parts[1][0])) {
  815. if (parts.size() < 3)
  816. return;
  817. auto channel = parts[1];
  818. auto nick = parts[2];
  819. auto reason = input.view().substring_view_starting_after_substring(nick);
  820. send_kick(channel, nick, reason);
  821. } else {
  822. auto* window = current_window();
  823. if (!window || window->type() != IRCWindow::Type::Channel)
  824. return;
  825. auto channel = window->channel().name();
  826. auto nick = parts[1];
  827. auto reason = input.view().substring_view_starting_after_substring(nick);
  828. send_kick(channel, nick, reason);
  829. }
  830. return;
  831. }
  832. if (command == "/LIST") {
  833. send_list();
  834. return;
  835. }
  836. if (command == "/QUERY") {
  837. if (parts.size() >= 2) {
  838. auto& query = ensure_query(parts[1]);
  839. IRCAppWindow::the().set_active_window(query.window());
  840. }
  841. return;
  842. }
  843. if (command == "/MSG") {
  844. if (parts.size() < 3)
  845. return;
  846. auto nick = parts[1];
  847. auto& query = ensure_query(nick);
  848. IRCAppWindow::the().set_active_window(query.window());
  849. query.say(input.view().substring_view_starting_after_substring(nick));
  850. return;
  851. }
  852. if (command == "/WHOIS") {
  853. if (parts.size() >= 2)
  854. send_whois(parts[1]);
  855. return;
  856. }
  857. }
  858. void IRCClient::change_nick(const String& nick)
  859. {
  860. send(String::format("NICK %s\r\n", nick.characters()));
  861. }
  862. void IRCClient::handle_list_channels_action()
  863. {
  864. send_list();
  865. }
  866. void IRCClient::handle_whois_action(const String& nick)
  867. {
  868. send_whois(nick);
  869. }
  870. void IRCClient::handle_open_query_action(const String& nick)
  871. {
  872. ensure_query(nick);
  873. }
  874. void IRCClient::handle_change_nick_action(const String& nick)
  875. {
  876. change_nick(nick);
  877. }
  878. void IRCClient::handle_change_topic_action(const String& channel, const String& topic)
  879. {
  880. send_topic(channel, topic);
  881. }
  882. void IRCClient::handle_invite_user_action(const String& channel, const String& nick)
  883. {
  884. send_invite(channel, nick);
  885. }
  886. void IRCClient::handle_banlist_action(const String& channel)
  887. {
  888. send_banlist(channel);
  889. }
  890. void IRCClient::handle_voice_user_action(const String& channel, const String& nick)
  891. {
  892. send_voice_user(channel, nick);
  893. }
  894. void IRCClient::handle_devoice_user_action(const String& channel, const String& nick)
  895. {
  896. send_devoice_user(channel, nick);
  897. }
  898. void IRCClient::handle_op_user_action(const String& channel, const String& nick)
  899. {
  900. send_op_user(channel, nick);
  901. }
  902. void IRCClient::handle_deop_user_action(const String& channel, const String& nick)
  903. {
  904. send_deop_user(channel, nick);
  905. }
  906. void IRCClient::handle_kick_user_action(const String& channel, const String& nick, const String& message)
  907. {
  908. send_kick(channel, nick, message);
  909. }
  910. void IRCClient::handle_close_query_action(const String& nick)
  911. {
  912. m_queries.remove(nick);
  913. m_client_window_list_model->update();
  914. }
  915. void IRCClient::handle_join_action(const String& channel)
  916. {
  917. join_channel(channel);
  918. }
  919. void IRCClient::handle_part_action(const String& channel)
  920. {
  921. part_channel(channel);
  922. }
  923. void IRCClient::handle_cycle_channel_action(const String& channel)
  924. {
  925. part_channel(channel);
  926. join_channel(channel);
  927. }
  928. void IRCClient::did_part_from_channel(Badge<IRCChannel>, IRCChannel& channel)
  929. {
  930. if (on_part_from_channel)
  931. on_part_from_channel(channel);
  932. }
  933. void IRCClient::send_ctcp_response(const StringView& peer, const StringView& payload)
  934. {
  935. StringBuilder builder;
  936. builder.append(0x01);
  937. builder.append(payload);
  938. builder.append(0x01);
  939. auto message = builder.to_string();
  940. send_notice(peer, message);
  941. }
  942. void IRCClient::handle_ctcp_request(const StringView& peer, const StringView& payload)
  943. {
  944. dbg() << "handle_ctcp_request: " << payload;
  945. if (payload == "VERSION") {
  946. auto version = ctcp_version_reply();
  947. if (version.is_empty())
  948. return;
  949. send_ctcp_response(peer, String::format("VERSION %s", version.characters()));
  950. return;
  951. }
  952. if (payload == "USERINFO") {
  953. auto userinfo = ctcp_userinfo_reply();
  954. if (userinfo.is_empty())
  955. return;
  956. send_ctcp_response(peer, String::format("USERINFO %s", userinfo.characters()));
  957. return;
  958. }
  959. if (payload == "FINGER") {
  960. auto finger = ctcp_finger_reply();
  961. if (finger.is_empty())
  962. return;
  963. send_ctcp_response(peer, String::format("FINGER %s", finger.characters()));
  964. return;
  965. }
  966. if (payload.starts_with("PING")) {
  967. send_ctcp_response(peer, payload);
  968. return;
  969. }
  970. }
  971. void IRCClient::handle_ctcp_response(const StringView& peer, const StringView& payload)
  972. {
  973. dbg() << "handle_ctcp_response(" << peer << "): " << payload;
  974. }