diff --git a/Applications/IRCClient/IRCChannel.cpp b/Applications/IRCClient/IRCChannel.cpp index 384cd018db..6e2a01b64c 100644 --- a/Applications/IRCClient/IRCChannel.cpp +++ b/Applications/IRCClient/IRCChannel.cpp @@ -55,3 +55,24 @@ void IRCChannel::say(const String& text) m_client.send_privmsg(m_name, text); add_message(' ', m_client.nickname(), text); } + +void IRCChannel::handle_join(const String& nick, const String& hostmask) +{ + if (nick == m_client.nickname()) + m_open = true; + add_message(' ', "", String::format("*** %s [%s] has joined %s", nick.characters(), hostmask.characters(), m_name.characters())); +} + +void IRCChannel::handle_part(const String& nick, const String& hostmask) +{ + if (nick == m_client.nickname()) + m_open = false; + add_message(' ', "", String::format("*** %s [%s] has parted from %s", nick.characters(), hostmask.characters(), m_name.characters())); +} + +void IRCChannel::handle_topic(const String& nick, const String& topic) +{ + if (nick == m_client.nickname()) + m_open = false; + add_message(' ', "", String::format("*** %s set topic to \"%s\"", nick.characters(), topic.characters())); +} diff --git a/Applications/IRCClient/IRCChannel.h b/Applications/IRCClient/IRCChannel.h index bb14292eda..94a037597c 100644 --- a/Applications/IRCClient/IRCChannel.h +++ b/Applications/IRCClient/IRCChannel.h @@ -39,14 +39,21 @@ public: int member_count() const { return m_members.size(); } String member_at(int i) { return m_members[i].name; } + void handle_join(const String& nick, const String& hostmask); + void handle_part(const String& nick, const String& hostmask); + void handle_topic(const String& nick, const String& topic); + IRCWindow& window() { return *m_window; } const IRCWindow& window() const { return *m_window; } + String topic() const { return m_topic; } + private: IRCChannel(IRCClient&, const String&); IRCClient& m_client; String m_name; + String m_topic; struct Member { String name; char prefix { 0 }; diff --git a/Applications/IRCClient/IRCClient.cpp b/Applications/IRCClient/IRCClient.cpp index 40d394d075..33e112d63d 100644 --- a/Applications/IRCClient/IRCClient.cpp +++ b/Applications/IRCClient/IRCClient.cpp @@ -11,9 +11,11 @@ #include #include -//#define IRC_DEBUG +#define IRC_DEBUG enum IRCNumeric { + RPL_TOPIC = 332, + RPL_TOPICWHOTIME = 333, RPL_NAMREPLY = 353, RPL_ENDOFNAMES = 366, }; @@ -201,6 +203,11 @@ void IRCClient::join_channel(const String& channel_name) send(String::format("JOIN %s\r\n", channel_name.characters())); } +void IRCClient::part_channel(const String& channel_name) +{ + send(String::format("PART %s\r\n", channel_name.characters())); +} + void IRCClient::handle(const Message& msg, const String&) { #ifdef IRC_DEBUG @@ -225,6 +232,9 @@ void IRCClient::handle(const Message& msg, const String&) case RPL_NAMREPLY: handle_namreply(msg); return; + case RPL_TOPIC: + handle_rpl_topic(msg); + return; } } @@ -234,6 +244,12 @@ void IRCClient::handle(const Message& msg, const String&) if (msg.command == "JOIN") return handle_join(msg); + if (msg.command == "PART") + return handle_part(msg); + + if (msg.command == "TOPIC") + return handle_topic(msg); + if (msg.command == "PRIVMSG") return handle_privmsg(msg); @@ -252,6 +268,8 @@ void IRCClient::handle_user_input_in_channel(const String& channel_name, const S { if (input.is_empty()) return; + if (input[0] == '/') + return handle_user_command(input); ensure_channel(channel_name).say(input); } @@ -259,6 +277,8 @@ void IRCClient::handle_user_input_in_query(const String& query_name, const Strin { if (input.is_empty()) return; + if (input[0] == '/') + return handle_user_command(input); ensure_query(query_name).say(input); } @@ -266,6 +286,8 @@ void IRCClient::handle_user_input_in_server(const String& input) { if (input.is_empty()) return; + if (input[0] == '/') + return handle_user_command(input); } bool IRCClient::is_nick_prefix(char ch) const @@ -349,8 +371,47 @@ void IRCClient::handle_join(const Message& msg) { if (msg.arguments.size() != 1) return; + auto prefix_parts = msg.prefix.split('!'); + if (prefix_parts.size() < 1) + return; + auto nick = prefix_parts[0]; auto& channel_name = msg.arguments[0]; - ensure_channel(channel_name); + ensure_channel(channel_name).handle_join(nick, msg.prefix); +} + +void IRCClient::handle_part(const Message& msg) +{ + if (msg.arguments.size() != 1) + return; + auto prefix_parts = msg.prefix.split('!'); + if (prefix_parts.size() < 1) + return; + auto nick = prefix_parts[0]; + auto& channel_name = msg.arguments[0]; + ensure_channel(channel_name).handle_part(nick, msg.prefix); +} + +void IRCClient::handle_topic(const Message& msg) +{ + if (msg.arguments.size() != 2) + return; + auto prefix_parts = msg.prefix.split('!'); + if (prefix_parts.size() < 1) + return; + auto nick = prefix_parts[0]; + auto& channel_name = msg.arguments[0]; + ensure_channel(channel_name).handle_topic(nick, msg.arguments[1]); +} + +void IRCClient::handle_rpl_topic(const Message& msg) +{ + if (msg.arguments.size() != 3) + return; + auto& nick = msg.arguments[0]; + auto& channel_name = msg.arguments[1]; + auto& topic = msg.arguments[2]; + ensure_channel(channel_name).handle_topic(nick, topic); + // FIXME: Handle RPL_TOPICWHOTIME so we can know who set it and when. } void IRCClient::handle_namreply(const Message& msg) @@ -403,3 +464,26 @@ void IRCClient::unregister_subwindow(IRCWindow& subwindow) } m_client_window_list_model->update(); } + +void IRCClient::handle_user_command(const String& input) +{ + auto parts = input.split(' '); + if (parts.is_empty()) + return; + auto command = parts[0].to_uppercase(); + if (command == "/JOIN") { + if (parts.size() >= 2) + join_channel(parts[1]); + return; + } + if (command == "/PART") { + if (parts.size() >= 2) + part_channel(parts[1]); + return; + } + if (command == "/QUERY") { + if (parts.size() >= 2) + ensure_query(parts[1]); + return; + } +} diff --git a/Applications/IRCClient/IRCClient.h b/Applications/IRCClient/IRCClient.h index eb3ac55b35..304353171e 100644 --- a/Applications/IRCClient/IRCClient.h +++ b/Applications/IRCClient/IRCClient.h @@ -27,6 +27,7 @@ public: String nickname() const { return m_nickname; } void join_channel(const String&); + void part_channel(const String&); bool is_nick_prefix(char) const; @@ -73,10 +74,14 @@ private: void send_privmsg(const String& target, const String&); void process_line(); void handle_join(const Message&); + void handle_part(const Message&); void handle_ping(const Message&); + void handle_topic(const Message&); + void handle_rpl_topic(const Message&); void handle_namreply(const Message&); void handle_privmsg(const Message&); void handle(const Message&, const String& verbatim); + void handle_user_command(const String&); String m_hostname; int m_port { 0 }; diff --git a/Applications/IRCClient/IRCLogBufferModel.cpp b/Applications/IRCClient/IRCLogBufferModel.cpp index 69835aaee9..64de1bbca1 100644 --- a/Applications/IRCClient/IRCLogBufferModel.cpp +++ b/Applications/IRCClient/IRCLogBufferModel.cpp @@ -51,7 +51,10 @@ GVariant IRCLogBufferModel::data(const GModelIndex& index, Role) const auto* tm = localtime(&entry.timestamp); return String::format("%02u:%02u:%02u", tm->tm_hour, tm->tm_min, tm->tm_sec); } - case Column::Name: return String::format("<%c%s>", entry.prefix ? entry.prefix : ' ', entry.sender.characters()); + case Column::Name: + if (entry.sender.is_empty()) + return String::empty(); + return String::format("<%c%s>", entry.prefix ? entry.prefix : ' ', entry.sender.characters()); case Column::Text: return entry.text; } ASSERT_NOT_REACHED();