IRCClient.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  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 <pwd.h>
  38. #include <stdio.h>
  39. #include <strings.h>
  40. #ifndef IRC_DEBUG
  41. # define IRC_DEBUG
  42. #endif
  43. enum IRCNumeric {
  44. RPL_WELCOME = 1,
  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(String server, int port)
  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. struct passwd* user_pw = getpwuid(getuid());
  71. m_socket = Core::TCPSocket::construct(this);
  72. m_nickname = m_config->read_entry("User", "Nickname", String::formatted("{}_seren1ty", user_pw->pw_name));
  73. if (server.is_empty()) {
  74. m_hostname = m_config->read_entry("Connection", "Server", "");
  75. m_port = m_config->read_num_entry("Connection", "Port", 6667);
  76. } else {
  77. m_hostname = server;
  78. m_port = port ? port : 6667;
  79. }
  80. m_show_join_part_messages = m_config->read_bool_entry("Messaging", "ShowJoinPartMessages", 1);
  81. m_show_nick_change_messages = m_config->read_bool_entry("Messaging", "ShowNickChangeMessages", 1);
  82. m_notify_on_message = m_config->read_bool_entry("Notifications", "NotifyOnMessage", 1);
  83. m_notify_on_mention = m_config->read_bool_entry("Notifications", "NotifyOnMention", 1);
  84. m_ctcp_version_reply = m_config->read_entry("CTCP", "VersionReply", "IRC Client [x86] / Serenity OS");
  85. m_ctcp_userinfo_reply = m_config->read_entry("CTCP", "UserInfoReply", user_pw->pw_name);
  86. m_ctcp_finger_reply = m_config->read_entry("CTCP", "FingerReply", user_pw->pw_name);
  87. }
  88. IRCClient::~IRCClient()
  89. {
  90. }
  91. void IRCClient::set_server(const String& hostname, int port)
  92. {
  93. m_hostname = hostname;
  94. m_port = port;
  95. m_config->write_entry("Connection", "Server", hostname);
  96. m_config->write_num_entry("Connection", "Port", port);
  97. m_config->sync();
  98. }
  99. void IRCClient::on_socket_connected()
  100. {
  101. m_notifier = Core::Notifier::construct(m_socket->fd(), Core::Notifier::Read);
  102. m_notifier->on_ready_to_read = [this] { receive_from_server(); };
  103. send_user();
  104. send_nick();
  105. }
  106. bool IRCClient::connect()
  107. {
  108. if (m_socket->is_connected())
  109. ASSERT_NOT_REACHED();
  110. m_socket->on_connected = [this] { on_socket_connected(); };
  111. bool success = m_socket->connect(m_hostname, m_port);
  112. if (!success)
  113. return false;
  114. return true;
  115. }
  116. void IRCClient::receive_from_server()
  117. {
  118. while (m_socket->can_read_line()) {
  119. auto line = m_socket->read_line(PAGE_SIZE);
  120. if (line.is_null()) {
  121. if (!m_socket->is_connected()) {
  122. out() << "IRCClient: Connection closed!";
  123. exit(1);
  124. }
  125. ASSERT_NOT_REACHED();
  126. }
  127. process_line(move(line));
  128. }
  129. }
  130. void IRCClient::process_line(ByteBuffer&& line)
  131. {
  132. Message msg;
  133. Vector<char, 32> prefix;
  134. Vector<char, 32> command;
  135. Vector<char, 256> current_parameter;
  136. enum {
  137. Start,
  138. InPrefix,
  139. InCommand,
  140. InStartOfParameter,
  141. InParameter,
  142. InTrailingParameter,
  143. } state
  144. = Start;
  145. for (size_t i = 0; i < line.size(); ++i) {
  146. char ch = line[i];
  147. if (ch == '\r')
  148. continue;
  149. if (ch == '\n')
  150. break;
  151. switch (state) {
  152. case Start:
  153. if (ch == ':') {
  154. state = InPrefix;
  155. continue;
  156. }
  157. state = InCommand;
  158. [[fallthrough]];
  159. case InCommand:
  160. if (ch == ' ') {
  161. state = InStartOfParameter;
  162. continue;
  163. }
  164. command.append(ch);
  165. continue;
  166. case InPrefix:
  167. if (ch == ' ') {
  168. state = InCommand;
  169. continue;
  170. }
  171. prefix.append(ch);
  172. continue;
  173. case InStartOfParameter:
  174. if (ch == ':') {
  175. state = InTrailingParameter;
  176. continue;
  177. }
  178. state = InParameter;
  179. [[fallthrough]];
  180. case InParameter:
  181. if (ch == ' ') {
  182. if (!current_parameter.is_empty())
  183. msg.arguments.append(String(current_parameter.data(), current_parameter.size()));
  184. current_parameter.clear_with_capacity();
  185. state = InStartOfParameter;
  186. continue;
  187. }
  188. current_parameter.append(ch);
  189. continue;
  190. case InTrailingParameter:
  191. current_parameter.append(ch);
  192. continue;
  193. }
  194. }
  195. if (!current_parameter.is_empty())
  196. msg.arguments.append(String::copy(current_parameter));
  197. msg.prefix = String::copy(prefix);
  198. msg.command = String::copy(command);
  199. handle(msg);
  200. }
  201. void IRCClient::send(const String& text)
  202. {
  203. if (!m_socket->send(text.bytes())) {
  204. perror("send");
  205. exit(1);
  206. }
  207. }
  208. void IRCClient::send_user()
  209. {
  210. send(String::formatted("USER {} 0 * :{}\r\n", m_nickname, m_nickname));
  211. }
  212. void IRCClient::send_nick()
  213. {
  214. send(String::formatted("NICK {}\r\n", m_nickname));
  215. }
  216. void IRCClient::send_pong(const String& server)
  217. {
  218. send(String::formatted("PONG {}\r\n", server));
  219. sleep(1);
  220. }
  221. void IRCClient::join_channel(const String& channel_name)
  222. {
  223. send(String::formatted("JOIN {}\r\n", channel_name));
  224. }
  225. void IRCClient::part_channel(const String& channel_name)
  226. {
  227. send(String::formatted("PART {}\r\n", channel_name));
  228. }
  229. void IRCClient::send_whois(const String& nick)
  230. {
  231. send(String::formatted("WHOIS {}\r\n", nick));
  232. }
  233. void IRCClient::handle(const Message& msg)
  234. {
  235. #ifdef IRC_DEBUG
  236. outln("IRCClient::execute: prefix='{}', command='{}', arguments={}",
  237. msg.prefix,
  238. msg.command,
  239. msg.arguments.size());
  240. size_t index = 0;
  241. for (auto& arg : msg.arguments)
  242. outln(" [{}]: {}", index++, arg);
  243. #endif
  244. auto numeric = msg.command.to_uint();
  245. if (numeric.has_value()) {
  246. switch (numeric.value()) {
  247. case RPL_WELCOME:
  248. return handle_rpl_welcome(msg);
  249. case RPL_WHOISCHANNELS:
  250. return handle_rpl_whoischannels(msg);
  251. case RPL_ENDOFWHO:
  252. return handle_rpl_endofwho(msg);
  253. case RPL_ENDOFWHOIS:
  254. return handle_rpl_endofwhois(msg);
  255. case RPL_ENDOFWHOWAS:
  256. return handle_rpl_endofwhowas(msg);
  257. case RPL_ENDOFMOTD:
  258. return handle_rpl_endofmotd(msg);
  259. case RPL_WHOISOPERATOR:
  260. return handle_rpl_whoisoperator(msg);
  261. case RPL_WHOISSERVER:
  262. return handle_rpl_whoisserver(msg);
  263. case RPL_WHOISUSER:
  264. return handle_rpl_whoisuser(msg);
  265. case RPL_WHOISIDLE:
  266. return handle_rpl_whoisidle(msg);
  267. case RPL_TOPICWHOTIME:
  268. return handle_rpl_topicwhotime(msg);
  269. case RPL_TOPIC:
  270. return handle_rpl_topic(msg);
  271. case RPL_NAMREPLY:
  272. return handle_rpl_namreply(msg);
  273. case RPL_ENDOFNAMES:
  274. return handle_rpl_endofnames(msg);
  275. case RPL_BANLIST:
  276. return handle_rpl_banlist(msg);
  277. case RPL_ENDOFBANLIST:
  278. return handle_rpl_endofbanlist(msg);
  279. case ERR_NOSUCHNICK:
  280. return handle_err_nosuchnick(msg);
  281. case ERR_UNKNOWNCOMMAND:
  282. return handle_err_unknowncommand(msg);
  283. case ERR_NICKNAMEINUSE:
  284. return handle_err_nicknameinuse(msg);
  285. }
  286. }
  287. if (msg.command == "PING")
  288. return handle_ping(msg);
  289. if (msg.command == "JOIN")
  290. return handle_join(msg);
  291. if (msg.command == "PART")
  292. return handle_part(msg);
  293. if (msg.command == "QUIT")
  294. return handle_quit(msg);
  295. if (msg.command == "TOPIC")
  296. return handle_topic(msg);
  297. if (msg.command == "PRIVMSG")
  298. return handle_privmsg_or_notice(msg, PrivmsgOrNotice::Privmsg);
  299. if (msg.command == "NOTICE")
  300. return handle_privmsg_or_notice(msg, PrivmsgOrNotice::Notice);
  301. if (msg.command == "NICK")
  302. return handle_nick(msg);
  303. if (msg.arguments.size() >= 2)
  304. add_server_message(String::formatted("[{}] {}", msg.command, msg.arguments[1]));
  305. }
  306. void IRCClient::add_server_message(const String& text, Color color)
  307. {
  308. m_log->add_message(0, "", text, color);
  309. m_server_subwindow->did_add_message();
  310. }
  311. void IRCClient::send_topic(const String& channel_name, const String& text)
  312. {
  313. send(String::formatted("TOPIC {} :{}\r\n", channel_name, text));
  314. }
  315. void IRCClient::send_invite(const String& channel_name, const String& nick)
  316. {
  317. send(String::formatted("INVITE {} {}\r\n", nick, channel_name));
  318. }
  319. void IRCClient::send_banlist(const String& channel_name)
  320. {
  321. send(String::formatted("MODE {} +b\r\n", channel_name));
  322. }
  323. void IRCClient::send_voice_user(const String& channel_name, const String& nick)
  324. {
  325. send(String::formatted("MODE {} +v {}\r\n", channel_name, nick));
  326. }
  327. void IRCClient::send_devoice_user(const String& channel_name, const String& nick)
  328. {
  329. send(String::formatted("MODE {} -v {}\r\n", channel_name, nick));
  330. }
  331. void IRCClient::send_hop_user(const String& channel_name, const String& nick)
  332. {
  333. send(String::formatted("MODE {} +h {}\r\n", channel_name, nick));
  334. }
  335. void IRCClient::send_dehop_user(const String& channel_name, const String& nick)
  336. {
  337. send(String::formatted("MODE {} -h {}\r\n", channel_name, nick));
  338. }
  339. void IRCClient::send_op_user(const String& channel_name, const String& nick)
  340. {
  341. send(String::formatted("MODE {} +o {}\r\n", channel_name, nick));
  342. }
  343. void IRCClient::send_deop_user(const String& channel_name, const String& nick)
  344. {
  345. send(String::formatted("MODE {} -o {}\r\n", channel_name, nick));
  346. }
  347. void IRCClient::send_kick(const String& channel_name, const String& nick, const String& comment)
  348. {
  349. send(String::formatted("KICK {} {} :{}\r\n", channel_name, nick, comment));
  350. }
  351. void IRCClient::send_list()
  352. {
  353. send("LIST\r\n");
  354. }
  355. void IRCClient::send_privmsg(const String& target, const String& text)
  356. {
  357. send(String::formatted("PRIVMSG {} :{}\r\n", target, text));
  358. }
  359. void IRCClient::send_notice(const String& target, const String& text)
  360. {
  361. send(String::formatted("NOTICE {} :{}\r\n", target, text));
  362. }
  363. void IRCClient::handle_user_input_in_channel(const String& channel_name, const String& input)
  364. {
  365. if (input.is_empty())
  366. return;
  367. if (input[0] == '/')
  368. return handle_user_command(input);
  369. ensure_channel(channel_name).say(input);
  370. }
  371. void IRCClient::handle_user_input_in_query(const String& query_name, const String& input)
  372. {
  373. if (input.is_empty())
  374. return;
  375. if (input[0] == '/')
  376. return handle_user_command(input);
  377. ensure_query(query_name).say(input);
  378. }
  379. void IRCClient::handle_user_input_in_server(const String& input)
  380. {
  381. if (input.is_empty())
  382. return;
  383. if (input[0] == '/')
  384. return handle_user_command(input);
  385. }
  386. String IRCClient::nick_without_prefix(const String& nick)
  387. {
  388. assert(!nick.is_empty());
  389. if (IRCClient::is_nick_prefix(nick[0]))
  390. return nick.substring(1, nick.length() - 1);
  391. return nick;
  392. }
  393. bool IRCClient::is_nick_prefix(char ch)
  394. {
  395. switch (ch) {
  396. case '@':
  397. case '+':
  398. case '~':
  399. case '&':
  400. case '%':
  401. return true;
  402. }
  403. return false;
  404. }
  405. bool IRCClient::is_channel_prefix(char ch)
  406. {
  407. switch (ch) {
  408. case '&':
  409. case '#':
  410. case '+':
  411. case '!':
  412. return true;
  413. }
  414. return false;
  415. }
  416. static bool has_ctcp_payload(const StringView& string)
  417. {
  418. return string.length() >= 2 && string[0] == 0x01 && string[string.length() - 1] == 0x01;
  419. }
  420. void IRCClient::handle_privmsg_or_notice(const Message& msg, PrivmsgOrNotice type)
  421. {
  422. if (msg.arguments.size() < 2)
  423. return;
  424. if (msg.prefix.is_empty())
  425. return;
  426. auto parts = msg.prefix.split('!');
  427. auto sender_nick = parts[0];
  428. auto target = msg.arguments[0];
  429. bool is_ctcp = has_ctcp_payload(msg.arguments[1]);
  430. #ifdef IRC_DEBUG
  431. outln("handle_privmsg_or_notice: type='{}'{}, sender_nick='{}', target='{}'",
  432. type == PrivmsgOrNotice::Privmsg ? "privmsg" : "notice",
  433. is_ctcp ? " (ctcp)" : "",
  434. sender_nick,
  435. target);
  436. #endif
  437. if (sender_nick.is_empty())
  438. return;
  439. char sender_prefix = 0;
  440. if (is_nick_prefix(sender_nick[0])) {
  441. sender_prefix = sender_nick[0];
  442. sender_nick = sender_nick.substring(1, sender_nick.length() - 1);
  443. }
  444. String message_text = msg.arguments[1];
  445. auto message_color = Color::Black;
  446. bool insert_as_raw_message = false;
  447. if (is_ctcp) {
  448. auto ctcp_payload = msg.arguments[1].substring_view(1, msg.arguments[1].length() - 2);
  449. if (type == PrivmsgOrNotice::Privmsg)
  450. handle_ctcp_request(sender_nick, ctcp_payload);
  451. else
  452. handle_ctcp_response(sender_nick, ctcp_payload);
  453. if (ctcp_payload.starts_with("ACTION")) {
  454. insert_as_raw_message = true;
  455. message_text = String::formatted("* {}{}", sender_nick, ctcp_payload.substring_view(6, ctcp_payload.length() - 6));
  456. message_color = Color::Magenta;
  457. } else {
  458. message_text = String::formatted("(CTCP) {}", ctcp_payload);
  459. message_color = Color::Blue;
  460. }
  461. }
  462. {
  463. auto it = m_channels.find(target);
  464. if (it != m_channels.end()) {
  465. if (insert_as_raw_message)
  466. (*it).value->add_message(message_text, message_color);
  467. else
  468. (*it).value->add_message(sender_prefix, sender_nick, message_text, message_color);
  469. return;
  470. }
  471. }
  472. // For NOTICE or CTCP messages, only put them in query if one already exists.
  473. // Otherwise, put them in the server window. This seems to match other clients.
  474. IRCQuery* query = nullptr;
  475. if (is_ctcp || type == PrivmsgOrNotice::Notice) {
  476. query = query_with_name(sender_nick);
  477. } else {
  478. query = &ensure_query(sender_nick);
  479. }
  480. if (query) {
  481. if (insert_as_raw_message)
  482. query->add_message(message_text, message_color);
  483. else
  484. query->add_message(sender_prefix, sender_nick, message_text, message_color);
  485. } else {
  486. add_server_message(String::formatted("<{}> {}", sender_nick, message_text), message_color);
  487. }
  488. }
  489. IRCQuery* IRCClient::query_with_name(const String& name)
  490. {
  491. return const_cast<IRCQuery*>(m_queries.get(name).value_or(nullptr));
  492. }
  493. IRCQuery& IRCClient::ensure_query(const String& name)
  494. {
  495. auto it = m_queries.find(name);
  496. if (it != m_queries.end())
  497. return *(*it).value;
  498. auto query = IRCQuery::create(*this, name);
  499. auto& query_reference = *query;
  500. m_queries.set(name, query);
  501. return query_reference;
  502. }
  503. IRCChannel& IRCClient::ensure_channel(const String& name)
  504. {
  505. auto it = m_channels.find(name);
  506. if (it != m_channels.end())
  507. return *(*it).value;
  508. auto channel = IRCChannel::create(*this, name);
  509. auto& channel_reference = *channel;
  510. m_channels.set(name, channel);
  511. return channel_reference;
  512. }
  513. void IRCClient::handle_ping(const Message& msg)
  514. {
  515. if (msg.arguments.size() < 1)
  516. return;
  517. m_log->add_message(0, "", "Ping? Pong!");
  518. send_pong(msg.arguments[0]);
  519. }
  520. void IRCClient::handle_join(const Message& msg)
  521. {
  522. if (msg.arguments.size() != 1)
  523. return;
  524. auto prefix_parts = msg.prefix.split('!');
  525. if (prefix_parts.size() < 1)
  526. return;
  527. auto nick = prefix_parts[0];
  528. auto& channel_name = msg.arguments[0];
  529. ensure_channel(channel_name).handle_join(nick, msg.prefix);
  530. }
  531. void IRCClient::handle_part(const Message& msg)
  532. {
  533. if (msg.arguments.size() < 1)
  534. return;
  535. auto prefix_parts = msg.prefix.split('!');
  536. if (prefix_parts.size() < 1)
  537. return;
  538. auto nick = prefix_parts[0];
  539. auto& channel_name = msg.arguments[0];
  540. ensure_channel(channel_name).handle_part(nick, msg.prefix);
  541. }
  542. void IRCClient::handle_quit(const Message& msg)
  543. {
  544. if (msg.arguments.size() < 1)
  545. return;
  546. auto prefix_parts = msg.prefix.split('!');
  547. if (prefix_parts.size() < 1)
  548. return;
  549. auto nick = prefix_parts[0];
  550. auto& message = msg.arguments[0];
  551. for (auto& it : m_channels) {
  552. it.value->handle_quit(nick, msg.prefix, message);
  553. }
  554. }
  555. void IRCClient::handle_nick(const Message& msg)
  556. {
  557. auto prefix_parts = msg.prefix.split('!');
  558. if (prefix_parts.size() < 1)
  559. return;
  560. auto old_nick = prefix_parts[0];
  561. if (msg.arguments.size() != 1)
  562. return;
  563. auto& new_nick = msg.arguments[0];
  564. if (old_nick == m_nickname)
  565. m_nickname = new_nick;
  566. if (m_show_nick_change_messages)
  567. add_server_message(String::formatted("~ {} changed nickname to {}", old_nick, new_nick));
  568. if (on_nickname_changed)
  569. on_nickname_changed(new_nick);
  570. for (auto& it : m_channels) {
  571. it.value->notify_nick_changed(old_nick, new_nick);
  572. }
  573. }
  574. void IRCClient::handle_topic(const Message& msg)
  575. {
  576. if (msg.arguments.size() != 2)
  577. return;
  578. auto prefix_parts = msg.prefix.split('!');
  579. if (prefix_parts.size() < 1)
  580. return;
  581. auto nick = prefix_parts[0];
  582. auto& channel_name = msg.arguments[0];
  583. ensure_channel(channel_name).handle_topic(nick, msg.arguments[1]);
  584. }
  585. void IRCClient::handle_rpl_welcome(const Message& msg)
  586. {
  587. if (msg.arguments.size() < 2)
  588. return;
  589. auto& welcome_message = msg.arguments[1];
  590. add_server_message(welcome_message);
  591. auto channel_str = m_config->read_entry("Connection", "AutoJoinChannels", "");
  592. if (channel_str.is_empty())
  593. return;
  594. dbgln("IRCClient: Channels to autojoin: {}", channel_str);
  595. auto channels = channel_str.split(',');
  596. for (auto& channel : channels) {
  597. join_channel(channel);
  598. dbgln("IRCClient: Auto joining channel: {}", channel);
  599. }
  600. }
  601. void IRCClient::handle_rpl_topic(const Message& msg)
  602. {
  603. if (msg.arguments.size() < 3)
  604. return;
  605. auto& channel_name = msg.arguments[1];
  606. auto& topic = msg.arguments[2];
  607. ensure_channel(channel_name).handle_topic({}, topic);
  608. }
  609. void IRCClient::handle_rpl_namreply(const Message& msg)
  610. {
  611. if (msg.arguments.size() < 4)
  612. return;
  613. auto& channel_name = msg.arguments[2];
  614. auto& channel = ensure_channel(channel_name);
  615. auto members = msg.arguments[3].split(' ');
  616. quick_sort(members, [](auto& a, auto& b) {
  617. return strcasecmp(a.characters(), b.characters()) < 0;
  618. });
  619. for (auto& member : members) {
  620. if (member.is_empty())
  621. continue;
  622. char prefix = 0;
  623. if (is_nick_prefix(member[0]))
  624. prefix = member[0];
  625. channel.add_member(member, prefix);
  626. }
  627. }
  628. void IRCClient::handle_rpl_endofnames(const Message&)
  629. {
  630. add_server_message("// End of NAMES");
  631. }
  632. void IRCClient::handle_rpl_banlist(const Message& msg)
  633. {
  634. if (msg.arguments.size() < 5)
  635. return;
  636. auto& channel = msg.arguments[1];
  637. auto& mask = msg.arguments[2];
  638. auto& user = msg.arguments[3];
  639. auto& datestamp = msg.arguments[4];
  640. add_server_message(String::formatted("* {}: {} on {} by {}", channel, mask, datestamp, user));
  641. }
  642. void IRCClient::handle_rpl_endofbanlist(const Message&)
  643. {
  644. add_server_message("// End of BANLIST");
  645. }
  646. void IRCClient::handle_rpl_endofwho(const Message&)
  647. {
  648. add_server_message("// End of WHO");
  649. }
  650. void IRCClient::handle_rpl_endofwhois(const Message&)
  651. {
  652. add_server_message("// End of WHOIS");
  653. }
  654. void IRCClient::handle_rpl_endofwhowas(const Message&)
  655. {
  656. add_server_message("// End of WHOWAS");
  657. }
  658. void IRCClient::handle_rpl_endofmotd(const Message&)
  659. {
  660. add_server_message("// End of MOTD");
  661. }
  662. void IRCClient::handle_rpl_whoisoperator(const Message& msg)
  663. {
  664. if (msg.arguments.size() < 2)
  665. return;
  666. auto& nick = msg.arguments[1];
  667. add_server_message(String::formatted("* {} is an IRC operator", nick));
  668. }
  669. void IRCClient::handle_rpl_whoisserver(const Message& msg)
  670. {
  671. if (msg.arguments.size() < 3)
  672. return;
  673. auto& nick = msg.arguments[1];
  674. auto& server = msg.arguments[2];
  675. add_server_message(String::formatted("* {} is using server {}", nick, server));
  676. }
  677. void IRCClient::handle_rpl_whoisuser(const Message& msg)
  678. {
  679. if (msg.arguments.size() < 6)
  680. return;
  681. auto& nick = msg.arguments[1];
  682. auto& username = msg.arguments[2];
  683. auto& host = msg.arguments[3];
  684. auto& asterisk = msg.arguments[4];
  685. auto& realname = msg.arguments[5];
  686. (void)asterisk;
  687. add_server_message(String::formatted("* {} is {}@{}, real name: {}", nick, username, host, realname));
  688. }
  689. void IRCClient::handle_rpl_whoisidle(const Message& msg)
  690. {
  691. if (msg.arguments.size() < 3)
  692. return;
  693. auto& nick = msg.arguments[1];
  694. auto& secs = msg.arguments[2];
  695. add_server_message(String::formatted("* {} is {} seconds idle", nick, secs));
  696. }
  697. void IRCClient::handle_rpl_whoischannels(const Message& msg)
  698. {
  699. if (msg.arguments.size() < 3)
  700. return;
  701. auto& nick = msg.arguments[1];
  702. auto& channel_list = msg.arguments[2];
  703. add_server_message(String::formatted("* {} is in channels {}", nick, channel_list));
  704. }
  705. void IRCClient::handle_rpl_topicwhotime(const Message& msg)
  706. {
  707. if (msg.arguments.size() < 4)
  708. return;
  709. auto& channel_name = msg.arguments[1];
  710. auto& nick = msg.arguments[2];
  711. auto setat = msg.arguments[3];
  712. auto setat_time = setat.to_uint();
  713. if (setat_time.has_value())
  714. setat = Core::DateTime::from_timestamp(setat_time.value()).to_string();
  715. ensure_channel(channel_name).add_message(String::formatted("*** (set by {} at {})", nick, setat), Color::Blue);
  716. }
  717. void IRCClient::handle_err_nosuchnick(const Message& msg)
  718. {
  719. if (msg.arguments.size() < 3)
  720. return;
  721. auto& nick = msg.arguments[1];
  722. auto& message = msg.arguments[2];
  723. add_server_message(String::formatted("* {} :{}", nick, message));
  724. }
  725. void IRCClient::handle_err_unknowncommand(const Message& msg)
  726. {
  727. if (msg.arguments.size() < 2)
  728. return;
  729. auto& cmd = msg.arguments[1];
  730. add_server_message(String::formatted("* Unknown command: {}", cmd));
  731. }
  732. void IRCClient::handle_err_nicknameinuse(const Message& msg)
  733. {
  734. if (msg.arguments.size() < 2)
  735. return;
  736. auto& nick = msg.arguments[1];
  737. add_server_message(String::formatted("* {} :Nickname in use", nick));
  738. }
  739. void IRCClient::register_subwindow(IRCWindow& subwindow)
  740. {
  741. if (subwindow.type() == IRCWindow::Server) {
  742. m_server_subwindow = &subwindow;
  743. subwindow.set_log_buffer(*m_log);
  744. }
  745. m_windows.append(&subwindow);
  746. m_client_window_list_model->update();
  747. }
  748. void IRCClient::unregister_subwindow(IRCWindow& subwindow)
  749. {
  750. if (subwindow.type() == IRCWindow::Server) {
  751. m_server_subwindow = &subwindow;
  752. }
  753. for (size_t i = 0; i < m_windows.size(); ++i) {
  754. if (m_windows.at(i) == &subwindow) {
  755. m_windows.remove(i);
  756. break;
  757. }
  758. }
  759. m_client_window_list_model->update();
  760. }
  761. void IRCClient::handle_user_command(const String& input)
  762. {
  763. auto parts = input.split_view(' ');
  764. if (parts.is_empty())
  765. return;
  766. auto command = String(parts[0]).to_uppercase();
  767. if (command == "/RAW") {
  768. if (parts.size() <= 1)
  769. return;
  770. int command_length = command.length() + 1;
  771. StringView raw_message = input.view().substring_view(command_length, input.view().length() - command_length);
  772. send(String::formatted("{}\r\n", raw_message));
  773. return;
  774. }
  775. if (command == "/NICK") {
  776. if (parts.size() >= 2)
  777. change_nick(parts[1]);
  778. return;
  779. }
  780. if (command == "/JOIN") {
  781. if (parts.size() >= 2)
  782. join_channel(parts[1]);
  783. return;
  784. }
  785. if (command == "/PART") {
  786. if (parts.size() >= 2) {
  787. auto channel = parts[1];
  788. part_channel(channel);
  789. } else {
  790. auto* window = current_window();
  791. if (!window || window->type() != IRCWindow::Type::Channel)
  792. return;
  793. auto channel = window->channel().name();
  794. join_channel(channel);
  795. }
  796. return;
  797. }
  798. if (command == "/CYCLE") {
  799. if (parts.size() >= 2) {
  800. auto channel = parts[1];
  801. part_channel(channel);
  802. join_channel(channel);
  803. } else {
  804. auto* window = current_window();
  805. if (!window || window->type() != IRCWindow::Type::Channel)
  806. return;
  807. auto channel = window->channel().name();
  808. part_channel(channel);
  809. join_channel(channel);
  810. }
  811. return;
  812. }
  813. if (command == "/BANLIST") {
  814. if (parts.size() >= 2) {
  815. auto channel = parts[1];
  816. send_banlist(channel);
  817. } else {
  818. auto* window = current_window();
  819. if (!window || window->type() != IRCWindow::Type::Channel)
  820. return;
  821. auto channel = window->channel().name();
  822. send_banlist(channel);
  823. }
  824. return;
  825. }
  826. if (command == "/ME") {
  827. if (parts.size() < 2)
  828. return;
  829. auto* window = current_window();
  830. if (!window)
  831. return;
  832. auto emote = input.view().substring_view_starting_after_substring(parts[0]);
  833. auto action_string = String::formatted("ACTION{}", emote);
  834. String peer;
  835. if (window->type() == IRCWindow::Type::Channel) {
  836. peer = window->channel().name();
  837. window->channel().add_message(String::formatted("* {}{}", m_nickname, emote), Gfx::Color::Magenta);
  838. } else if (window->type() == IRCWindow::Type::Query) {
  839. peer = window->query().name();
  840. window->query().add_message(String::formatted("* {}{}", m_nickname, emote), Gfx::Color::Magenta);
  841. } else {
  842. return;
  843. }
  844. send_ctcp_request(peer, action_string);
  845. return;
  846. }
  847. if (command == "/TOPIC") {
  848. if (parts.size() < 2)
  849. return;
  850. if (parts[1].is_empty())
  851. return;
  852. if (is_channel_prefix(parts[1][0])) {
  853. if (parts.size() < 3)
  854. return;
  855. auto channel = parts[1];
  856. auto topic = input.view().substring_view_starting_after_substring(channel);
  857. send_topic(channel, topic);
  858. } else {
  859. auto* window = current_window();
  860. if (!window || window->type() != IRCWindow::Type::Channel)
  861. return;
  862. auto channel = window->channel().name();
  863. auto topic = input.view().substring_view_starting_after_substring(parts[0]);
  864. send_topic(channel, topic);
  865. }
  866. return;
  867. }
  868. if (command == "/KICK") {
  869. if (parts.size() < 2)
  870. return;
  871. if (parts[1].is_empty())
  872. return;
  873. if (is_channel_prefix(parts[1][0])) {
  874. if (parts.size() < 3)
  875. return;
  876. auto channel = parts[1];
  877. auto nick = parts[2];
  878. auto reason = input.view().substring_view_starting_after_substring(nick);
  879. send_kick(channel, nick, reason);
  880. } else {
  881. auto* window = current_window();
  882. if (!window || window->type() != IRCWindow::Type::Channel)
  883. return;
  884. auto channel = window->channel().name();
  885. auto nick = parts[1];
  886. auto reason = input.view().substring_view_starting_after_substring(nick);
  887. send_kick(channel, nick, reason);
  888. }
  889. return;
  890. }
  891. if (command == "/LIST") {
  892. send_list();
  893. return;
  894. }
  895. if (command == "/QUERY") {
  896. if (parts.size() >= 2) {
  897. auto& query = ensure_query(parts[1]);
  898. IRCAppWindow::the().set_active_window(query.window());
  899. }
  900. return;
  901. }
  902. if (command == "/MSG") {
  903. if (parts.size() < 3)
  904. return;
  905. auto nick = parts[1];
  906. auto& query = ensure_query(nick);
  907. IRCAppWindow::the().set_active_window(query.window());
  908. query.say(input.view().substring_view_starting_after_substring(nick));
  909. return;
  910. }
  911. if (command == "/WHOIS") {
  912. if (parts.size() >= 2)
  913. send_whois(parts[1]);
  914. return;
  915. }
  916. }
  917. void IRCClient::change_nick(const String& nick)
  918. {
  919. send(String::formatted("NICK {}\r\n", nick));
  920. }
  921. void IRCClient::handle_list_channels_action()
  922. {
  923. send_list();
  924. }
  925. void IRCClient::handle_whois_action(const String& nick)
  926. {
  927. send_whois(nick);
  928. }
  929. void IRCClient::handle_ctcp_user_action(const String& nick, const String& message)
  930. {
  931. send_ctcp_request(nick, message);
  932. }
  933. void IRCClient::handle_open_query_action(const String& nick)
  934. {
  935. ensure_query(nick);
  936. }
  937. void IRCClient::handle_change_nick_action(const String& nick)
  938. {
  939. change_nick(nick);
  940. }
  941. void IRCClient::handle_change_topic_action(const String& channel, const String& topic)
  942. {
  943. send_topic(channel, topic);
  944. }
  945. void IRCClient::handle_invite_user_action(const String& channel, const String& nick)
  946. {
  947. send_invite(channel, nick);
  948. }
  949. void IRCClient::handle_banlist_action(const String& channel)
  950. {
  951. send_banlist(channel);
  952. }
  953. void IRCClient::handle_voice_user_action(const String& channel, const String& nick)
  954. {
  955. send_voice_user(channel, nick);
  956. }
  957. void IRCClient::handle_devoice_user_action(const String& channel, const String& nick)
  958. {
  959. send_devoice_user(channel, nick);
  960. }
  961. void IRCClient::handle_hop_user_action(const String& channel, const String& nick)
  962. {
  963. send_hop_user(channel, nick);
  964. }
  965. void IRCClient::handle_dehop_user_action(const String& channel, const String& nick)
  966. {
  967. send_dehop_user(channel, nick);
  968. }
  969. void IRCClient::handle_op_user_action(const String& channel, const String& nick)
  970. {
  971. send_op_user(channel, nick);
  972. }
  973. void IRCClient::handle_deop_user_action(const String& channel, const String& nick)
  974. {
  975. send_deop_user(channel, nick);
  976. }
  977. void IRCClient::handle_kick_user_action(const String& channel, const String& nick, const String& message)
  978. {
  979. send_kick(channel, nick, message);
  980. }
  981. void IRCClient::handle_close_query_action(const String& nick)
  982. {
  983. m_queries.remove(nick);
  984. m_client_window_list_model->update();
  985. }
  986. void IRCClient::handle_join_action(const String& channel)
  987. {
  988. join_channel(channel);
  989. }
  990. void IRCClient::handle_part_action(const String& channel)
  991. {
  992. part_channel(channel);
  993. }
  994. void IRCClient::handle_cycle_channel_action(const String& channel)
  995. {
  996. part_channel(channel);
  997. join_channel(channel);
  998. }
  999. void IRCClient::did_part_from_channel(Badge<IRCChannel>, IRCChannel& channel)
  1000. {
  1001. if (on_part_from_channel)
  1002. on_part_from_channel(channel);
  1003. }
  1004. void IRCClient::send_ctcp_response(const StringView& peer, const StringView& payload)
  1005. {
  1006. StringBuilder builder;
  1007. builder.append(0x01);
  1008. builder.append(payload);
  1009. builder.append(0x01);
  1010. auto message = builder.to_string();
  1011. send_notice(peer, message);
  1012. }
  1013. void IRCClient::send_ctcp_request(const StringView& peer, const StringView& payload)
  1014. {
  1015. StringBuilder builder;
  1016. builder.append(0x01);
  1017. builder.append(payload);
  1018. builder.append(0x01);
  1019. auto message = builder.to_string();
  1020. send_privmsg(peer, message);
  1021. }
  1022. void IRCClient::handle_ctcp_request(const StringView& peer, const StringView& payload)
  1023. {
  1024. dbgln("handle_ctcp_request: {}", payload);
  1025. if (payload == "VERSION") {
  1026. auto version = ctcp_version_reply();
  1027. if (version.is_empty())
  1028. return;
  1029. send_ctcp_response(peer, String::formatted("VERSION {}", version));
  1030. return;
  1031. }
  1032. if (payload == "USERINFO") {
  1033. auto userinfo = ctcp_userinfo_reply();
  1034. if (userinfo.is_empty())
  1035. return;
  1036. send_ctcp_response(peer, String::formatted("USERINFO {}", userinfo));
  1037. return;
  1038. }
  1039. if (payload == "FINGER") {
  1040. auto finger = ctcp_finger_reply();
  1041. if (finger.is_empty())
  1042. return;
  1043. send_ctcp_response(peer, String::formatted("FINGER {}", finger));
  1044. return;
  1045. }
  1046. if (payload.starts_with("PING")) {
  1047. send_ctcp_response(peer, payload);
  1048. return;
  1049. }
  1050. }
  1051. void IRCClient::handle_ctcp_response(const StringView& peer, const StringView& payload)
  1052. {
  1053. dbgln("handle_ctcp_response({}): {}", peer, payload);
  1054. }