IRCClient.cpp 32 KB

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