diff --git a/degesch.c b/degesch.c index e561173..7b0af26 100644 --- a/degesch.c +++ b/degesch.c @@ -2381,6 +2381,155 @@ irc_process_message (const struct irc_message *msg, 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) +{ + // :!@ + 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 ----------------------------------------------------- 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 handle_command_quit (struct app_context *ctx, char *arguments) { @@ -2599,10 +2850,14 @@ g_command_handlers[] = "[message]" }, { "buffer", handle_command_buffer, "Manage buffers", "list | clear | move | { close [ | ] } | " }, + + { "msg", handle_command_msg, "Send message to a nick or channel", + " " }, + { "query", handle_command_query, "Send a private message to a nick", + " " }, + { "notice", handle_command_notice, "Send notice to a nick or channel", + " " }, #if 0 - { "msg", NULL, "", "" }, - { "query", NULL, "", "" }, - { "notice", NULL, "", "" }, { "ctcp", NULL, "", "" }, { "me", NULL, "", "" }, #endif @@ -2722,58 +2977,7 @@ send_message_to_target (struct app_context *ctx, return; } - // We don't always have the full info for message splitting - int space_in_one_message = 0; - if (ctx->irc_user_host) - // :!@ PRIVMSG : - 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); - } + SEND_AUTOSPLIT_PRIVMSG (ctx, target, message); } static void