diff --git a/degesch.c b/degesch.c index 528611a..f5310d8 100644 --- a/degesch.c +++ b/degesch.c @@ -1310,10 +1310,9 @@ struct user REF_COUNTABLE_HEADER char *nickname; ///< Literal nickname - // TODO: write code to poll for the away status bool away; ///< User is away - struct user_channel *channels; ///< Channels the user is on + struct user_channel *channels; ///< Channels the user is on (with us) }; static struct ispect_field g_user_ispect[] = @@ -1379,6 +1378,8 @@ struct channel { REF_COUNTABLE_HEADER + struct server *s; ///< Server + char *name; ///< Channel name char *topic; ///< Channel topic @@ -1391,6 +1392,7 @@ struct channel size_t users_len; ///< User count bool left_manually; ///< Don't rejoin on reconnect + bool show_names_after_who; ///< RPL_ENDOFWHO delays RPL_ENDOFNAMES }; static struct ispect_field g_channel_ispect[] = @@ -1405,12 +1407,12 @@ static struct ispect_field g_channel_ispect[] = }; static struct channel * -channel_new (char *name, char *topic) +channel_new (struct server *s, char *name) { struct channel *self = xcalloc (1, sizeof *self); self->ref_count = 1; + self->s = s; self->name = name; - self->topic = topic; self->no_param_modes = str_make (); self->param_modes = str_map_make (free); self->names_buf = strv_make (); @@ -1714,7 +1716,8 @@ struct server char *irc_user_host; ///< Our current user@host bool autoaway_active; ///< Autoaway is currently active - bool cap_echo_message; ///< Whether the server echos messages + bool cap_echo_message; ///< Whether the server echoes messages + bool cap_away_notify; ///< Whether we get AWAY notifications // Server-specific information (from RPL_ISUPPORT): @@ -2329,7 +2332,8 @@ static struct config_schema g_config_server[] = .comment = "Capabilities to use if supported by server", .type = CONFIG_ITEM_STRING_ARRAY, .validate = config_validate_nonjunk_string, - .default_ = "\"multi-prefix,invite-notify,server-time,echo-message\"" }, + .default_ = "\"multi-prefix,invite-notify,server-time,echo-message," + "message-tags,away-notify\"" }, { .name = "tls", .comment = "Whether to use TLS", @@ -4523,6 +4527,11 @@ irc_channel_unlink_user user_channel_destroy (iter); } + // TODO: poll the away status for users we don't share a channel with. + // It might or might not be worth to auto-set this on with RPL_AWAY. + if (!user->channels && user != channel->s->irc_user) + user->away = false; + // Then just unlink the user from the channel LIST_UNLINK (channel->users, channel_user); channel_user_destroy (channel_user); @@ -4545,7 +4554,7 @@ irc_make_channel (struct server *s, char *name) { hard_assert (!str_map_find (&s->irc_channels, name)); - struct channel *channel = channel_new (name, NULL); + struct channel *channel = channel_new (s, name); (void) channel_weak_ref (channel, irc_channel_on_destroy, s); str_map_set (&s->irc_channels, channel->name, channel); return channel; @@ -4571,6 +4580,9 @@ irc_remove_user_from_channel (struct user *user, struct channel *channel) static void irc_left_channel (struct channel *channel) { + strv_reset (&channel->names_buf); + channel->show_names_after_who = false; + LIST_FOR_EACH (struct channel_user, iter, channel->users) irc_channel_unlink_user (channel, iter); } @@ -6456,6 +6468,21 @@ irc_process_sent_message (const struct irc_message *msg, struct server *s) // --- Input handling ---------------------------------------------------------- +static void +irc_handle_away (struct server *s, const struct irc_message *msg) +{ + if (!msg->prefix) + return; + + char *nickname = irc_cut_nickname (msg->prefix); + struct user *user = str_map_find (&s->irc_users, nickname); + free (nickname); + + // Let's allow the server to make us away + if (user) + user->away = !!msg->params.len; +} + static void irc_handle_cap (struct server *s, const struct irc_message *msg) { @@ -6483,6 +6510,8 @@ irc_handle_cap (struct server *s, const struct irc_message *msg) } if (!strcasecmp_ascii (cap, "echo-message")) s->cap_echo_message = active; + if (!strcasecmp_ascii (cap, "away-notify")) + s->cap_away_notify = active; } irc_send (s, "CAP END"); } @@ -6596,6 +6625,9 @@ irc_handle_join (struct server *s, const struct irc_message *msg) str_reset (&channel->no_param_modes); str_map_clear (&channel->param_modes); irc_send (s, "MODE %s", channel_name); + + if ((channel->show_names_after_who = s->cap_away_notify)) + irc_send (s, "WHO %s", channel_name); } // Add the user to the channel @@ -7153,6 +7185,7 @@ irc_handle_topic (struct server *s, const struct irc_message *msg) static struct irc_handler g_irc_handlers[] = { // This list needs to stay sorted + { "AWAY", irc_handle_away }, { "CAP", irc_handle_cap }, { "ERROR", irc_handle_error }, { "INVITE", irc_handle_invite }, @@ -7359,11 +7392,16 @@ make_channel_users_list (struct server *s, struct channel *channel) qsort (entries, n_users, sizeof *entries, channel_user_sort_entry_cmp); + // Make names of users that are away italicised, constructing a formatter + // and adding a new attribute seems like unnecessary work struct str list = str_make (); for (i = 0; i < n_users; i++) { - irc_get_channel_user_prefix (s, entries[i].channel_user, &list); - str_append (&list, entries[i].channel_user->user->nickname); + struct channel_user *channel_user = entries[i].channel_user; + if (channel_user->user->away) str_append_c (&list, '\x1d'); + irc_get_channel_user_prefix (s, channel_user, &list); + str_append (&list, channel_user->user->nickname); + if (channel_user->user->away) str_append_c (&list, '\x1d'); str_append_c (&list, ' '); } if (list.len) @@ -7392,6 +7430,17 @@ irc_sync_channel_user (struct server *s, struct channel *channel, cstr_set (&channel_user->prefixes, xstrdup (prefixes)); } +static void +irc_process_names_finish (struct server *s, struct channel *channel) +{ + struct buffer *buffer = str_map_find (&s->irc_buffer_map, channel->name); + if (buffer) + { + log_server_status (s, buffer, "Users on #S: #&m", + channel->name, make_channel_users_list (s, channel)); + } +} + static void irc_process_names (struct server *s, struct channel *channel) { @@ -7421,15 +7470,8 @@ irc_process_names (struct server *s, struct channel *channel) str_map_free (&present); strv_reset (&channel->names_buf); - char *all_users = make_channel_users_list (s, channel); - struct buffer *buffer = str_map_find (&s->irc_buffer_map, channel->name); - if (buffer) - { - log_server_status (s, buffer, "Users on #S: #S", - channel->name, all_users); - } - - free (all_users); + if (!channel->show_names_after_who) + irc_process_names_finish (s, channel); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -7453,6 +7495,47 @@ irc_handle_rpl_endofnames (struct server *s, const struct irc_message *msg) irc_process_names (s, channel); } +static bool +irc_handle_rpl_whoreply (struct server *s, const struct irc_message *msg) +{ + if (msg->params.len < 7) + return false; + + // Sequence: channel, user, host, server, nick, chars + const char *channel_name = msg->params.vector[1]; + const char *nickname = msg->params.vector[5]; + const char *chars = msg->params.vector[6]; + + struct channel *channel = str_map_find (&s->irc_channels, channel_name); + struct user *user = str_map_find (&s->irc_users, nickname); + + // This makes sense to set only with the away-notify capability so far. + // We track ourselves by other means and we can't track PM-only users yet. + if (!channel || !channel->show_names_after_who + || !user || user == s->irc_user || !user->channels) + return false; + + user->away = *chars == 'G'; + return true; +} + +static bool +irc_handle_rpl_endofwho (struct server *s, const struct irc_message *msg) +{ + if (msg->params.len < 2) + return false; + + const char *target = msg->params.vector[1]; + + struct channel *channel = str_map_find (&s->irc_channels, target); + if (!channel || !channel->show_names_after_who) + return false; + + irc_process_names_finish (s, channel); + channel->show_names_after_who = false; + return true; +} + static void irc_handle_rpl_topic (struct server *s, const struct irc_message *msg) { @@ -7790,9 +7873,12 @@ irc_process_numeric (struct server *s, if (s->irc_user) s->irc_user->away = false; break; - case IRC_RPL_LIST: case IRC_RPL_WHOREPLY: + if (irc_handle_rpl_whoreply (s, msg)) buffer = NULL; break; case IRC_RPL_ENDOFWHO: + if (irc_handle_rpl_endofwho (s, msg)) buffer = NULL; break; + + case IRC_RPL_LIST: case IRC_ERR_UNKNOWNCOMMAND: case IRC_ERR_NEEDMOREPARAMS: @@ -8255,6 +8341,7 @@ server_remove (struct app_context *ctx, struct server *s) static void server_rename (struct app_context *ctx, struct server *s, const char *new_name) { + hard_assert (!str_map_find (&ctx->servers, new_name)); str_map_set (&ctx->servers, new_name, str_map_steal (&ctx->servers, s->name));