degesch: halfplement /msg, /query, /notice
Which involved some refactoring.
This commit is contained in:
		
							parent
							
								
									2633eda69a
								
							
						
					
					
						commit
						c088f081d7
					
				
							
								
								
									
										314
									
								
								degesch.c
									
									
									
									
									
								
							
							
						
						
									
										314
									
								
								degesch.c
									
									
									
									
									
								
							| @ -2381,6 +2381,155 @@ irc_process_message (const struct irc_message *msg, | |||||||
| 		irc_process_numeric (ctx, msg, numeric); | 		irc_process_numeric (ctx, msg, numeric); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // --- Message autosplitting magic ---------------------------------------------
 | ||||||
|  | 
 | ||||||
|  | static bool | ||||||
|  | wrap_text (const char *message, | ||||||
|  | 	int line_max, struct str_vector *output, struct error **e) | ||||||
|  | { | ||||||
|  | 	// Attempt to split the message if it doesn't completely fit into a single
 | ||||||
|  | 	// IRC protocol message while trying not to break UTF-8.  Unicode can still
 | ||||||
|  | 	// end up being wrong, though.  As well as any mIRC formatting.
 | ||||||
|  | 	//
 | ||||||
|  | 	// TODO: at least try to word-wrap if nothing else
 | ||||||
|  | 
 | ||||||
|  | 	for (int message_left = strlen (message); message_left; ) | ||||||
|  | 	{ | ||||||
|  | 		struct str m; | ||||||
|  | 		str_init (&m); | ||||||
|  | 
 | ||||||
|  | 		int part_left = MIN (line_max, message_left); | ||||||
|  | 		bool empty = true; | ||||||
|  | 		while (true) | ||||||
|  | 		{ | ||||||
|  | 			const char *next = utf8_next (message, message_left); | ||||||
|  | 			hard_assert (next); | ||||||
|  | 
 | ||||||
|  | 			int char_len = message - next; | ||||||
|  | 			if (char_len > part_left) | ||||||
|  | 				break; | ||||||
|  | 
 | ||||||
|  | 			str_append_data (&m, message, char_len); | ||||||
|  | 
 | ||||||
|  | 			message += char_len; | ||||||
|  | 			message_left -= char_len; | ||||||
|  | 			empty = false; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (!empty) | ||||||
|  | 			str_vector_add (output, m.str); | ||||||
|  | 
 | ||||||
|  | 		str_free (&m); | ||||||
|  | 
 | ||||||
|  | 		if (empty) | ||||||
|  | 		{ | ||||||
|  | 			// Well, that's just weird
 | ||||||
|  | 			error_set (e, | ||||||
|  | 				"Message splitting was unsuccessful as there was " | ||||||
|  | 				"too little room for UTF-8 characters"); | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | ||||||
|  | 
 | ||||||
|  | /// Automatically splits messages that arrive at other clients with our prefix
 | ||||||
|  | /// so that they don't arrive cut off by the server
 | ||||||
|  | static bool | ||||||
|  | irc_autosplit_message (struct app_context *ctx, const char *message, | ||||||
|  | 	int fixed_part, struct str_vector *output, struct error **e) | ||||||
|  | { | ||||||
|  | 	// :<nick>!<user>@<host> <fixed-part><message>
 | ||||||
|  | 	int space_in_one_message = 0; | ||||||
|  | 	if (ctx->irc_user_host) | ||||||
|  | 		space_in_one_message = 510 | ||||||
|  | 			- 1 - (int) strlen (ctx->irc_user->nickname) | ||||||
|  | 			- 1 - (int) strlen (ctx->irc_user_host) | ||||||
|  | 			- 1 - fixed_part; | ||||||
|  | 
 | ||||||
|  | 	// However we don't always have the full info for message splitting
 | ||||||
|  | 	if (!space_in_one_message) | ||||||
|  | 		str_vector_add (output, message); | ||||||
|  | 	else if (!wrap_text (message, space_in_one_message, output, e)) | ||||||
|  | 		return false; | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | struct send_autosplit_args; | ||||||
|  | 
 | ||||||
|  | typedef void (*send_autosplit_logger_fn) (struct app_context *ctx, | ||||||
|  | 	struct send_autosplit_args *args, struct buffer *buffer, const char *line); | ||||||
|  | 
 | ||||||
|  | struct send_autosplit_args | ||||||
|  | { | ||||||
|  | 	const char *command;                ///< E.g. PRIVMSG or NOTICE
 | ||||||
|  | 	const char *target;                 ///< User or channel
 | ||||||
|  | 	const char *message;                ///< A message to be autosplit
 | ||||||
|  | 	send_autosplit_logger_fn logger;    ///< Logger for all resulting lines
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | send_autosplit_message (struct app_context *ctx, struct send_autosplit_args a) | ||||||
|  | { | ||||||
|  | 	struct buffer *buffer = str_map_find (&ctx->irc_buffer_map, a.target); | ||||||
|  | 	int fixed_part = strlen (a.command) + 1 + strlen (a.target) + 1 + 1; | ||||||
|  | 
 | ||||||
|  | 	struct str_vector lines; | ||||||
|  | 	str_vector_init (&lines); | ||||||
|  | 	struct error *e = NULL; | ||||||
|  | 	if (!irc_autosplit_message (ctx, a.message, fixed_part, &lines, &e)) | ||||||
|  | 	{ | ||||||
|  | 		buffer_send_error (ctx, | ||||||
|  | 			buffer ? buffer : ctx->server_buffer, "%s", e->message); | ||||||
|  | 		error_free (e); | ||||||
|  | 		goto end; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for (size_t i = 0; i < lines.len; i++) | ||||||
|  | 	{ | ||||||
|  | 		irc_send (ctx, "%s %s :%s", a.command, a.target, lines.vector[i]); | ||||||
|  | 		a.logger (ctx, &a, buffer, lines.vector[i]); | ||||||
|  | 	} | ||||||
|  | end: | ||||||
|  | 	str_vector_free (&lines); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | log_outcoming_privmsg (struct app_context *ctx, | ||||||
|  | 	struct send_autosplit_args *a, struct buffer *buffer, const char *line) | ||||||
|  | { | ||||||
|  | 	if (buffer) | ||||||
|  | 		buffer_send (ctx, buffer, BUFFER_LINE_PRIVMSG, 0, | ||||||
|  | 			ctx->irc_user->nickname, NULL, "%s", line); | ||||||
|  | 	else | ||||||
|  | 		// TODO: fix logging
 | ||||||
|  | 		buffer_send (ctx, ctx->server_buffer, BUFFER_LINE_STATUS, 0, | ||||||
|  | 			NULL, NULL, "MSG(%s): %s", a->target, line); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #define SEND_AUTOSPLIT_PRIVMSG(ctx, target, message)                           \ | ||||||
|  | 	send_autosplit_message ((ctx), (struct send_autosplit_args)                \ | ||||||
|  | 		{ "PRIVMSG", (target), (message), log_outcoming_privmsg }) | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | log_outcoming_notice (struct app_context *ctx, | ||||||
|  | 	struct send_autosplit_args *a, struct buffer *buffer, const char *line) | ||||||
|  | { | ||||||
|  | 	if (buffer) | ||||||
|  | 		buffer_send (ctx, buffer, BUFFER_LINE_NOTICE, 0, | ||||||
|  | 			ctx->irc_user->nickname, NULL, "%s", line); | ||||||
|  | 	else | ||||||
|  | 		// TODO: fix logging
 | ||||||
|  | 		buffer_send (ctx, ctx->server_buffer, BUFFER_LINE_STATUS, 0, | ||||||
|  | 			NULL, NULL, "Notice -> %s: %s", a->target, line); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #define SEND_AUTOSPLIT_NOTICE(ctx, target, message)                            \ | ||||||
|  | 	send_autosplit_message ((ctx), (struct send_autosplit_args)                \ | ||||||
|  | 		{ "NOTICE", (target), (message), log_outcoming_notice }) | ||||||
|  | 
 | ||||||
| // --- User input handling -----------------------------------------------------
 | // --- User input handling -----------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| static void handle_command_help (struct app_context *, char *); | static void handle_command_help (struct app_context *, char *); | ||||||
| @ -2490,6 +2639,108 @@ handle_command_buffer (struct app_context *ctx, char *arguments) | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void | ||||||
|  | handle_command_msg (struct app_context *ctx, char *arguments) | ||||||
|  | { | ||||||
|  | 	if (ctx->current_buffer->type == BUFFER_GLOBAL) | ||||||
|  | 	{ | ||||||
|  | 		buffer_send_error (ctx, ctx->current_buffer, | ||||||
|  | 			"Can't send messages from a global buffer"); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (ctx->irc_fd == -1) | ||||||
|  | 	{ | ||||||
|  | 		buffer_send_error (ctx, ctx->server_buffer, "Not connected"); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!*arguments) | ||||||
|  | 	{ | ||||||
|  | 		// TODO: show usage or something
 | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	char *target = cut_word (&arguments); | ||||||
|  | 	if (!*arguments) | ||||||
|  | 		buffer_send_error (ctx, ctx->server_buffer, "No text to send"); | ||||||
|  | 	else | ||||||
|  | 		SEND_AUTOSPLIT_PRIVMSG (ctx, target, arguments); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | handle_command_query (struct app_context *ctx, char *arguments) | ||||||
|  | { | ||||||
|  | 	if (ctx->current_buffer->type == BUFFER_GLOBAL) | ||||||
|  | 	{ | ||||||
|  | 		buffer_send_error (ctx, ctx->current_buffer, | ||||||
|  | 			"Can't send messages from a global buffer"); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (ctx->irc_fd == -1) | ||||||
|  | 	{ | ||||||
|  | 		buffer_send_error (ctx, ctx->server_buffer, "Not connected"); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!*arguments) | ||||||
|  | 	{ | ||||||
|  | 		// TODO: show usage or something
 | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	char *target = cut_word (&arguments); | ||||||
|  | 	if (irc_is_channel (ctx, target)) | ||||||
|  | 	{ | ||||||
|  | 		buffer_send_error (ctx, ctx->server_buffer, "Cannot query a channel"); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!*arguments) | ||||||
|  | 		buffer_send_error (ctx, ctx->server_buffer, "No text to send"); | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		struct buffer *buffer = str_map_find (&ctx->irc_buffer_map, target); | ||||||
|  | 		if (!buffer) | ||||||
|  | 		{ | ||||||
|  | 			// TODO: create a buffer for this user
 | ||||||
|  | 			// TODO: see irc_handle_privmsg
 | ||||||
|  | 		} | ||||||
|  | 		buffer_activate (ctx, buffer); | ||||||
|  | 		SEND_AUTOSPLIT_PRIVMSG (ctx, target, arguments); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void | ||||||
|  | handle_command_notice (struct app_context *ctx, char *arguments) | ||||||
|  | { | ||||||
|  | 	if (ctx->current_buffer->type == BUFFER_GLOBAL) | ||||||
|  | 	{ | ||||||
|  | 		buffer_send_error (ctx, ctx->current_buffer, | ||||||
|  | 			"Can't send messages from a global buffer"); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (ctx->irc_fd == -1) | ||||||
|  | 	{ | ||||||
|  | 		buffer_send_error (ctx, ctx->server_buffer, "Not connected"); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!*arguments) | ||||||
|  | 	{ | ||||||
|  | 		// TODO: show usage or something
 | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	char *target = cut_word (&arguments); | ||||||
|  | 	if (!*arguments) | ||||||
|  | 		buffer_send_error (ctx, ctx->server_buffer, "No text to send"); | ||||||
|  | 	else | ||||||
|  | 		SEND_AUTOSPLIT_NOTICE (ctx, target, arguments); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void | static void | ||||||
| handle_command_quit (struct app_context *ctx, char *arguments) | handle_command_quit (struct app_context *ctx, char *arguments) | ||||||
| { | { | ||||||
| @ -2599,10 +2850,14 @@ g_command_handlers[] = | |||||||
| 	  "[message]" }, | 	  "[message]" }, | ||||||
| 	{ "buffer",  handle_command_buffer, "Manage buffers", | 	{ "buffer",  handle_command_buffer, "Manage buffers", | ||||||
| 	  "list | clear | move | { close [<number> | <name>] } | <number>" }, | 	  "list | clear | move | { close [<number> | <name>] } | <number>" }, | ||||||
|  | 
 | ||||||
|  | 	{ "msg",     handle_command_msg,    "Send message to a nick or channel", | ||||||
|  | 	  "<target> <message>" }, | ||||||
|  | 	{ "query",   handle_command_query,  "Send a private message to a nick", | ||||||
|  | 	  "<nick> <message>" }, | ||||||
|  | 	{ "notice",  handle_command_notice, "Send notice to a nick or channel", | ||||||
|  | 	  "<target> <message>" }, | ||||||
| #if 0 | #if 0 | ||||||
| 	{ "msg",     NULL, "", "" }, |  | ||||||
| 	{ "query",   NULL, "", "" }, |  | ||||||
| 	{ "notice",  NULL, "", "" }, |  | ||||||
| 	{ "ctcp",    NULL, "", "" }, | 	{ "ctcp",    NULL, "", "" }, | ||||||
| 	{ "me",      NULL, "", "" }, | 	{ "me",      NULL, "", "" }, | ||||||
| #endif | #endif | ||||||
| @ -2722,58 +2977,7 @@ send_message_to_target (struct app_context *ctx, | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// We don't always have the full info for message splitting
 | 	SEND_AUTOSPLIT_PRIVMSG (ctx, target, message); | ||||||
| 	int space_in_one_message = 0; |  | ||||||
| 	if (ctx->irc_user_host) |  | ||||||
| 		// :<nick>!<user>@<host> PRIVMSG <target> :<message>
 |  | ||||||
| 		space_in_one_message = 510 |  | ||||||
| 			- 1 - (int) strlen (ctx->irc_user->nickname) |  | ||||||
| 			- 1 - (int) strlen (ctx->irc_user_host) |  | ||||||
| 			- 1 - 7 - 1 - strlen (target) - 1 - 1; |  | ||||||
| 
 |  | ||||||
| 	// Attempt to split the message if it doesn't completely fit into
 |  | ||||||
| 	// a single IRC protocol message while trying not to break UTF-8.
 |  | ||||||
| 	// Unicode can still end up being wrong, though.
 |  | ||||||
| 	// TODO: at least try to word-wrap if nothing else
 |  | ||||||
| 	for (int message_left = strlen (message); message_left; ) |  | ||||||
| 	{ |  | ||||||
| 		struct str m; |  | ||||||
| 		str_init (&m); |  | ||||||
| 
 |  | ||||||
| 		int part_left = MIN (space_in_one_message, message_left); |  | ||||||
| 		if (!space_in_one_message) |  | ||||||
| 			part_left = message_left; |  | ||||||
| 
 |  | ||||||
| 		bool empty = true; |  | ||||||
| 		while (true) |  | ||||||
| 		{ |  | ||||||
| 			const char *next = utf8_next (message, message_left); |  | ||||||
| 			hard_assert (next); |  | ||||||
| 
 |  | ||||||
| 			int char_len = message - next; |  | ||||||
| 			if (char_len > part_left) |  | ||||||
| 				break; |  | ||||||
| 
 |  | ||||||
| 			str_append_data (&m, message, char_len); |  | ||||||
| 
 |  | ||||||
| 			message += char_len; |  | ||||||
| 			message_left -= char_len; |  | ||||||
| 			empty = false; |  | ||||||
| 		} |  | ||||||
| 		if (empty) |  | ||||||
| 		{ |  | ||||||
| 			// Well, that's just weird
 |  | ||||||
| 			buffer_send_error (ctx, buffer, "%s", |  | ||||||
| 				"Message splitting was unsuccessful as there was " |  | ||||||
| 				"too little room for UTF-8 characters"); |  | ||||||
| 			message_left = 0; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		irc_send (ctx, "PRIVMSG %s :%s", target, m.str); |  | ||||||
| 		buffer_send (ctx, buffer, BUFFER_LINE_PRIVMSG, 0, |  | ||||||
| 			ctx->irc_user->nickname, NULL, "%s", m.str); |  | ||||||
| 		str_free (&m); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void | static void | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user