IRCClient.cpp 33 KB

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