|
|
|
@ -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)); |
|
|
|
|
|
|
|
|
|