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