degesch: watch away statuses with away-notify/WHO
We're not going to implement polling. Polling is complex. Freenode supports away-notify.
This commit is contained in:
		
							parent
							
								
									e1a4fab40d
								
							
						
					
					
						commit
						2c48bc9959
					
				
							
								
								
									
										125
									
								
								degesch.c
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								degesch.c
									
									
									
									
									
								
							| @ -1310,10 +1310,9 @@ struct user | |||||||
| 	REF_COUNTABLE_HEADER | 	REF_COUNTABLE_HEADER | ||||||
| 
 | 
 | ||||||
| 	char *nickname;                     ///< Literal nickname
 | 	char *nickname;                     ///< Literal nickname
 | ||||||
| 	// TODO: write code to poll for the away status
 |  | ||||||
| 	bool away;                          ///< User is away
 | 	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[] = | static struct ispect_field g_user_ispect[] = | ||||||
| @ -1379,6 +1378,8 @@ struct channel | |||||||
| { | { | ||||||
| 	REF_COUNTABLE_HEADER | 	REF_COUNTABLE_HEADER | ||||||
| 
 | 
 | ||||||
|  | 	struct server *s;                   ///< Server
 | ||||||
|  | 
 | ||||||
| 	char *name;                         ///< Channel name
 | 	char *name;                         ///< Channel name
 | ||||||
| 	char *topic;                        ///< Channel topic
 | 	char *topic;                        ///< Channel topic
 | ||||||
| 
 | 
 | ||||||
| @ -1391,6 +1392,7 @@ struct channel | |||||||
| 	size_t users_len;                   ///< User count
 | 	size_t users_len;                   ///< User count
 | ||||||
| 
 | 
 | ||||||
| 	bool left_manually;                 ///< Don't rejoin on reconnect
 | 	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[] = | static struct ispect_field g_channel_ispect[] = | ||||||
| @ -1405,12 +1407,12 @@ static struct ispect_field g_channel_ispect[] = | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static struct channel * | static struct channel * | ||||||
| channel_new (char *name, char *topic) | channel_new (struct server *s, char *name) | ||||||
| { | { | ||||||
| 	struct channel *self = xcalloc (1, sizeof *self); | 	struct channel *self = xcalloc (1, sizeof *self); | ||||||
| 	self->ref_count = 1; | 	self->ref_count = 1; | ||||||
|  | 	self->s = s; | ||||||
| 	self->name = name; | 	self->name = name; | ||||||
| 	self->topic = topic; |  | ||||||
| 	self->no_param_modes = str_make (); | 	self->no_param_modes = str_make (); | ||||||
| 	self->param_modes = str_map_make (free); | 	self->param_modes = str_map_make (free); | ||||||
| 	self->names_buf = strv_make (); | 	self->names_buf = strv_make (); | ||||||
| @ -1714,7 +1716,8 @@ struct server | |||||||
| 	char *irc_user_host;                ///< Our current user@host
 | 	char *irc_user_host;                ///< Our current user@host
 | ||||||
| 	bool autoaway_active;               ///< Autoaway is currently active
 | 	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):
 | 	// 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", | 	  .comment   = "Capabilities to use if supported by server", | ||||||
| 	  .type      = CONFIG_ITEM_STRING_ARRAY, | 	  .type      = CONFIG_ITEM_STRING_ARRAY, | ||||||
| 	  .validate  = config_validate_nonjunk_string, | 	  .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", | 	{ .name      = "tls", | ||||||
| 	  .comment   = "Whether to use TLS", | 	  .comment   = "Whether to use TLS", | ||||||
| @ -4523,6 +4527,11 @@ irc_channel_unlink_user | |||||||
| 			user_channel_destroy (iter); | 			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
 | 	// Then just unlink the user from the channel
 | ||||||
| 	LIST_UNLINK (channel->users, channel_user); | 	LIST_UNLINK (channel->users, channel_user); | ||||||
| 	channel_user_destroy (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)); | 	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); | 	(void) channel_weak_ref (channel, irc_channel_on_destroy, s); | ||||||
| 	str_map_set (&s->irc_channels, channel->name, channel); | 	str_map_set (&s->irc_channels, channel->name, channel); | ||||||
| 	return channel; | 	return channel; | ||||||
| @ -4571,6 +4580,9 @@ irc_remove_user_from_channel (struct user *user, struct channel *channel) | |||||||
| static void | static void | ||||||
| irc_left_channel (struct channel *channel) | 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) | 	LIST_FOR_EACH (struct channel_user, iter, channel->users) | ||||||
| 		irc_channel_unlink_user (channel, iter); | 		irc_channel_unlink_user (channel, iter); | ||||||
| } | } | ||||||
| @ -6456,6 +6468,21 @@ irc_process_sent_message (const struct irc_message *msg, struct server *s) | |||||||
| 
 | 
 | ||||||
| // --- Input handling ----------------------------------------------------------
 | // --- 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 | static void | ||||||
| irc_handle_cap (struct server *s, const struct irc_message *msg) | 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")) | 			if (!strcasecmp_ascii (cap, "echo-message")) | ||||||
| 				s->cap_echo_message = active; | 				s->cap_echo_message = active; | ||||||
|  | 			if (!strcasecmp_ascii (cap, "away-notify")) | ||||||
|  | 				s->cap_away_notify = active; | ||||||
| 		} | 		} | ||||||
| 		irc_send (s, "CAP END"); | 		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_reset (&channel->no_param_modes); | ||||||
| 		str_map_clear (&channel->param_modes); | 		str_map_clear (&channel->param_modes); | ||||||
| 		irc_send (s, "MODE %s", channel_name); | 		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
 | 	// 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[] = | static struct irc_handler g_irc_handlers[] = | ||||||
| { | { | ||||||
| 	// This list needs to stay sorted
 | 	// This list needs to stay sorted
 | ||||||
|  | 	{ "AWAY",    irc_handle_away    }, | ||||||
| 	{ "CAP",     irc_handle_cap     }, | 	{ "CAP",     irc_handle_cap     }, | ||||||
| 	{ "ERROR",   irc_handle_error   }, | 	{ "ERROR",   irc_handle_error   }, | ||||||
| 	{ "INVITE",  irc_handle_invite  }, | 	{ "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); | 	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 (); | 	struct str list = str_make (); | ||||||
| 	for (i = 0; i < n_users; i++) | 	for (i = 0; i < n_users; i++) | ||||||
| 	{ | 	{ | ||||||
| 		irc_get_channel_user_prefix (s, entries[i].channel_user, &list); | 		struct channel_user *channel_user = entries[i].channel_user; | ||||||
| 		str_append (&list, entries[i].channel_user->user->nickname); | 		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, ' '); | 		str_append_c (&list, ' '); | ||||||
| 	} | 	} | ||||||
| 	if (list.len) | 	if (list.len) | ||||||
| @ -7392,6 +7430,17 @@ irc_sync_channel_user (struct server *s, struct channel *channel, | |||||||
| 		cstr_set (&channel_user->prefixes, xstrdup (prefixes)); | 		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 | static void | ||||||
| irc_process_names (struct server *s, struct channel *channel) | 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); | 	str_map_free (&present); | ||||||
| 	strv_reset (&channel->names_buf); | 	strv_reset (&channel->names_buf); | ||||||
| 
 | 
 | ||||||
| 	char *all_users = make_channel_users_list (s, channel); | 	if (!channel->show_names_after_who) | ||||||
| 	struct buffer *buffer = str_map_find (&s->irc_buffer_map, channel->name); | 		irc_process_names_finish (s, channel); | ||||||
| 	if (buffer) |  | ||||||
| 	{ |  | ||||||
| 		log_server_status (s, buffer, "Users on #S: #S", |  | ||||||
| 			channel->name, all_users); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	free (all_users); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||||
| @ -7453,6 +7495,47 @@ irc_handle_rpl_endofnames (struct server *s, const struct irc_message *msg) | |||||||
| 		irc_process_names (s, channel); | 		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 | static void | ||||||
| irc_handle_rpl_topic (struct server *s, const struct irc_message *msg) | 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; | 		if (s->irc_user) s->irc_user->away = false; | ||||||
| 		break; | 		break; | ||||||
| 
 | 
 | ||||||
| 	case IRC_RPL_LIST: |  | ||||||
| 	case IRC_RPL_WHOREPLY: | 	case IRC_RPL_WHOREPLY: | ||||||
|  | 		if (irc_handle_rpl_whoreply (s, msg))  buffer = NULL; break; | ||||||
| 	case IRC_RPL_ENDOFWHO: | 	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_UNKNOWNCOMMAND: | ||||||
| 	case IRC_ERR_NEEDMOREPARAMS: | 	case IRC_ERR_NEEDMOREPARAMS: | ||||||
| @ -8255,6 +8341,7 @@ server_remove (struct app_context *ctx, struct server *s) | |||||||
| static void | static void | ||||||
| server_rename (struct app_context *ctx, struct server *s, const char *new_name) | 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_set (&ctx->servers, new_name, | ||||||
| 		str_map_steal (&ctx->servers, s->name)); | 		str_map_steal (&ctx->servers, s->name)); | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user