aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2020-10-16 16:45:40 +0200
committerPřemysl Eric Janouch <p@janouch.name>2020-10-16 21:17:57 +0200
commit2c48bc99590efce278a436e3ce8d78cd04356686 (patch)
tree5db4e3d9449ab49f1e19ef9b7c378125f1ed7cbd
parente1a4fab40d325e9479970ab38184e1de91163d39 (diff)
downloadxK-2c48bc99590efce278a436e3ce8d78cd04356686.tar.gz
xK-2c48bc99590efce278a436e3ce8d78cd04356686.tar.xz
xK-2c48bc99590efce278a436e3ce8d78cd04356686.zip
degesch: watch away statuses with away-notify/WHO
We're not going to implement polling. Polling is complex. Freenode supports away-notify.
-rw-r--r--degesch.c125
1 files changed, 106 insertions, 19 deletions
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);
}
@@ -6457,6 +6469,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)
{
if (msg->params.len < 2)
@@ -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)
@@ -7393,6 +7431,17 @@ irc_sync_channel_user (struct server *s, struct channel *channel,
}
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)
{
struct str_map present = str_map_make (NULL);
@@ -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));