degesch: halfplement /msg, /query, /notice

Which involved some refactoring.
This commit is contained in:
Přemysl Eric Janouch 2015-04-23 02:48:25 +02:00
parent 2633eda69a
commit c088f081d7
1 changed files with 259 additions and 55 deletions

314
degesch.c
View File

@ -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)
{
// :<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 -----------------------------------------------------
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 [<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
{ "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)
// :<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);
}
SEND_AUTOSPLIT_PRIVMSG (ctx, target, message);
}
static void