diff --git a/degesch.c b/degesch.c index ad4abb8..2e6e436 100644 --- a/degesch.c +++ b/degesch.c @@ -5681,6 +5681,25 @@ dump_matching_options // --- User input handling ----------------------------------------------------- +// HANDLER_NEEDS_REG is primarily for message sending commands, +// as they may want to log buffer lines and use our current nickname + +enum handler_flags +{ + HANDLER_SERVER = (1 << 0), ///< Server context required + HANDLER_NEEDS_REG = (1 << 1), ///< Server registration required + HANDLER_CHANNEL_FIRST = (1 << 2), ///< Channel required, first argument + HANDLER_CHANNEL_LAST = (1 << 3) ///< Channel required, last argument +}; + +struct handler_args +{ + struct buffer *buffer; ///< Current buffer + struct server *s; ///< Related server + const char *channel_name; ///< Related channel name + char *arguments; ///< Command arguments +}; + /// Cuts the longest non-whitespace portion of text and advances the pointer static char * cut_word (char **s) @@ -5693,8 +5712,11 @@ cut_word (char **s) return start; } +/// Validates a word to be cut from a string +typedef bool (*word_validator_fn) (void *, char *); + static char * -maybe_cut_word (char **s, bool (*validator) (void *, char *), void *user_data) +maybe_cut_word (char **s, word_validator_fn validator, void *user_data) { char *start = *s; size_t word_len = strcspn (*s, WORD_BREAKING_CHARS); @@ -5712,6 +5734,51 @@ maybe_cut_word (char **s, bool (*validator) (void *, char *), void *user_data) return start; } +static char * +maybe_cut_word_from_end (char **s, word_validator_fn validator, void *user_data) +{ + // Find the start and end of the last word + char *start = *s, *end = start + strlen (start); + while (end > start && strchr (WORD_BREAKING_CHARS, end [-1])) + end--; + char *word = end; + while (word > start && !strchr (WORD_BREAKING_CHARS, word[-1])) + word--; + + // There's just one word at maximum, starting at the beginning + if (word == start) + return maybe_cut_word (s, validator, user_data); + + char *tmp = xstrndup (word, word - start); + bool ok = validator (user_data, tmp); + free (tmp); + + if (!ok) + return NULL; + + // It doesn't start at the beginning, cut it off and return it + word[-1] = *end = '\0'; + return word; +} + +static bool +validate_channel_name (void *user_data, char *word) +{ + return irc_is_channel (user_data, word); +} + +static char * +try_get_channel (struct handler_args *a, + char *(*cutter) (char **, word_validator_fn, void *)) +{ + char *channel_name = cutter (&a->arguments, validate_channel_name, a->s); + if (channel_name) + return channel_name; + if (a->buffer->type == BUFFER_CHANNEL) + return a->buffer->channel->name; + return NULL; +} + static bool try_handle_buffer_goto (struct app_context *ctx, const char *word) { @@ -5738,66 +5805,6 @@ try_decode_buffer (struct app_context *ctx, const char *word) return buffer; } -static bool -server_command_check (struct app_context *ctx, const char *action, - bool need_registration) -{ - // "need_registration" is primarily for message sending commands, - // as they may want to log buffer lines and use our current nickname - - if (ctx->current_buffer->type == BUFFER_GLOBAL) - // XXX: couldn't we just pass the name of the user command here? - // That doesn't actually concern the function but rather its callers. - buffer_send_error (ctx, ctx->current_buffer, - "Can't do this from a global buffer (%s)", action); - else - { - struct server *s = ctx->current_buffer->server; - if (!irc_is_connected (s)) - buffer_send_error (ctx, s->buffer, "Not connected"); - else if (s->state != IRC_REGISTERED && need_registration) - buffer_send_error (ctx, s->buffer, "Not registered"); - else - return true; - } - return false; -} - -#define SERVER_COMMAND(name, need_registration) \ - if (!server_command_check (ctx, (name), (need_registration))) \ - return true; \ - struct server *s = ctx->current_buffer->server; - -#define CHANNEL_COMMAND(name, need_registration) \ - SERVER_COMMAND ((name), (need_registration)) \ - char *channel_name = try_get_channel (ctx, &arguments); \ - if (!channel_name) \ - { \ - buffer_send_error (ctx, ctx->current_buffer, \ - "Can't %s: %s", (name), \ - "no channel name given and this buffer is not a channel"); \ - return true; \ - } - -static bool -validate_channel_name (void *user_data, char *word) -{ - struct server *s = user_data; - return irc_is_channel (s, word); -} - -static char * -try_get_channel (struct app_context *ctx, char **arguments) -{ - struct server *s = ctx->current_buffer->server; - char *channel_name = maybe_cut_word (arguments, validate_channel_name, s); - if (channel_name) - return channel_name; - if (ctx->current_buffer->type == BUFFER_CHANNEL) - return ctx->current_buffer->channel->name; - return NULL; -} - static void show_buffers_list (struct app_context *ctx) { @@ -5838,9 +5845,9 @@ handle_buffer_close (struct app_context *ctx, char *arguments) } static bool -handle_command_buffer (struct app_context *ctx, char *arguments) +handle_command_buffer (struct app_context *ctx, struct handler_args *a) { - char *action = cut_word (&arguments); + char *action = cut_word (&a->arguments); if (try_handle_buffer_goto (ctx, action)) return true; @@ -5858,7 +5865,7 @@ handle_command_buffer (struct app_context *ctx, char *arguments) // we will probably need to extend liberty for this } else if (!strcasecmp_ascii (action, "close")) - handle_buffer_close (ctx, arguments); + handle_buffer_close (ctx, a->arguments); else return false; @@ -6006,11 +6013,11 @@ handle_command_set_assign } static bool -handle_command_set (struct app_context *ctx, char *arguments) +handle_command_set (struct app_context *ctx, struct handler_args *a) { char *option = "*"; - if (*arguments) - option = cut_word (&arguments); + if (*a->arguments) + option = cut_word (&a->arguments); struct str_vector all; str_vector_init (&all); @@ -6019,23 +6026,23 @@ handle_command_set (struct app_context *ctx, char *arguments) bool result = true; if (!all.len) buffer_send_error (ctx, ctx->global_buffer, "No matches: %s", option); - else if (!*arguments) + else if (!*a->arguments) { buffer_send_status (ctx, ctx->global_buffer, "%s", ""); for (size_t i = 0; i < all.len; i++) buffer_send_status (ctx, ctx->global_buffer, "%s", all.vector[i]); } else - result = handle_command_set_assign (ctx, &all, arguments); + result = handle_command_set_assign (ctx, &all, a->arguments); str_vector_free (&all); return result; } static bool -handle_command_save (struct app_context *ctx, char *arguments) +handle_command_save (struct app_context *ctx, struct handler_args *a) { - if (*arguments) + if (*a->arguments) return false; struct str data; @@ -6060,104 +6067,93 @@ handle_command_save (struct app_context *ctx, char *arguments) } static bool -handle_command_msg (struct app_context *ctx, char *arguments) +handle_command_msg (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("send messages", true) - - if (!*arguments) + if (!*a->arguments) return false; - char *target = cut_word (&arguments); - if (!*arguments) - buffer_send_error (ctx, s->buffer, "No text to send"); + char *target = cut_word (&a->arguments); + if (!*a->arguments) + buffer_send_error (ctx, a->s->buffer, "No text to send"); else - SEND_AUTOSPLIT_PRIVMSG (s, target, arguments); + SEND_AUTOSPLIT_PRIVMSG (a->s, target, a->arguments); return true; } static bool -handle_command_query (struct app_context *ctx, char *arguments) +handle_command_query (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("send messages", true) - - if (!*arguments) + if (!*a->arguments) return false; - char *target = cut_word (&arguments); - if (irc_is_channel (s, target)) - buffer_send_error (ctx, s->buffer, "Cannot query a channel"); - else if (!*arguments) - buffer_send_error (ctx, s->buffer, "No text to send"); + char *target = cut_word (&a->arguments); + if (irc_is_channel (a->s, target)) + buffer_send_error (ctx, a->s->buffer, "Cannot query a channel"); + else if (!*a->arguments) + buffer_send_error (ctx, a->s->buffer, "No text to send"); else { - buffer_activate (ctx, irc_get_or_make_user_buffer (s, target)); - SEND_AUTOSPLIT_PRIVMSG (s, target, arguments); + buffer_activate (ctx, irc_get_or_make_user_buffer (a->s, target)); + SEND_AUTOSPLIT_PRIVMSG (a->s, target, a->arguments); } return true; } static bool -handle_command_notice (struct app_context *ctx, char *arguments) +handle_command_notice (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("send messages", true) - - if (!*arguments) + if (!*a->arguments) return false; - char *target = cut_word (&arguments); - if (!*arguments) - buffer_send_error (ctx, s->buffer, "No text to send"); + char *target = cut_word (&a->arguments); + if (!*a->arguments) + buffer_send_error (ctx, a->s->buffer, "No text to send"); else - SEND_AUTOSPLIT_NOTICE (s, target, arguments); + SEND_AUTOSPLIT_NOTICE (a->s, target, a->arguments); return true; } static bool -handle_command_ctcp (struct app_context *ctx, char *arguments) +handle_command_ctcp (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("send messages", true) - - if (!*arguments) + if (!*a->arguments) return false; - char *target = cut_word (&arguments); - if (!*arguments) + char *target = cut_word (&a->arguments); + if (!*a->arguments) return false; - char *tag = cut_word (&arguments); + char *tag = cut_word (&a->arguments); for (char *p = tag; *p; p++) *p = toupper_ascii (*p); - if (*arguments) - irc_send (s, "PRIVMSG %s :\x01%s %s\x01", target, tag, arguments); + if (*a->arguments) + irc_send (a->s, "PRIVMSG %s :\x01%s %s\x01", target, tag, a->arguments); else - irc_send (s, "PRIVMSG %s :\x01%s\x01", target, tag); + irc_send (a->s, "PRIVMSG %s :\x01%s\x01", target, tag); - buffer_send_status (ctx, s->buffer, - "CTCP query to %s: %s", target, tag); + buffer_send_status (ctx, a->s->buffer, "CTCP query to %s: %s", target, tag); return true; } static bool -handle_command_me (struct app_context *ctx, char *arguments) +handle_command_me (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("send messages", true) - - if (ctx->current_buffer->type == BUFFER_CHANNEL) - SEND_AUTOSPLIT_ACTION (s, - ctx->current_buffer->channel->name, arguments); - else if (ctx->current_buffer->type == BUFFER_PM) - SEND_AUTOSPLIT_ACTION (s, - ctx->current_buffer->user->nickname, arguments); + if (a->buffer->type == BUFFER_CHANNEL) + SEND_AUTOSPLIT_ACTION (a->s, + a->buffer->channel->name, a->arguments); + else if (a->buffer->type == BUFFER_PM) + SEND_AUTOSPLIT_ACTION (a->s, + a->buffer->user->nickname, a->arguments); else - buffer_send_error (ctx, s->buffer, + buffer_send_error (ctx, a->s->buffer, "Can't do this from a server buffer (%s)", "send CTCP actions"); return true; } static bool -handle_command_quit (struct app_context *ctx, char *arguments) +handle_command_quit (struct app_context *ctx, struct handler_args *a) { struct str_map_iter iter; str_map_iter_init (&iter, &ctx->servers); @@ -6166,7 +6162,7 @@ handle_command_quit (struct app_context *ctx, char *arguments) while ((s = str_map_iter_next (&iter))) { if (irc_is_connected (s)) - irc_initiate_disconnect (s, *arguments ? arguments : NULL); + irc_initiate_disconnect (s, *a->arguments ? a->arguments : NULL); } initiate_quit (ctx); @@ -6174,63 +6170,57 @@ handle_command_quit (struct app_context *ctx, char *arguments) } static bool -handle_command_join (struct app_context *ctx, char *arguments) +handle_command_join (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("join", true) - // XXX: send the last known channel key? - if (irc_is_channel (s, arguments)) + if (irc_is_channel (a->s, a->arguments)) // XXX: we may want to split the list of channels - irc_send (s, "JOIN %s", arguments); - else if (ctx->current_buffer->type != BUFFER_CHANNEL) - buffer_send_error (ctx, ctx->current_buffer, + irc_send (a->s, "JOIN %s", a->arguments); + else if (a->buffer->type != BUFFER_CHANNEL) + buffer_send_error (ctx, a->buffer, "%s: %s", "Can't join", "no channel name given and this buffer is not a channel"); // TODO: have a better way of checking if we're on the channel - else if (ctx->current_buffer->channel->users) - buffer_send_error (ctx, ctx->current_buffer, - "%s: %s", "Can't join", - "you already are on the channel"); - else if (*arguments) - irc_send (s, "JOIN %s :%s", - ctx->current_buffer->channel->name, arguments); + else if (a->buffer->channel->users) + buffer_send_error (ctx, a->buffer, + "%s: %s", "Can't join", "you already are on the channel"); + else if (*a->arguments) + irc_send (a->s, "JOIN %s :%s", a->buffer->channel->name, a->arguments); else - irc_send (s, "JOIN %s", ctx->current_buffer->channel->name); + irc_send (a->s, "JOIN %s", a->buffer->channel->name); return true; } static bool -handle_command_part (struct app_context *ctx, char *arguments) +handle_command_part (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("part", true) - - if (irc_is_channel (s, arguments)) + if (irc_is_channel (a->s, a->arguments)) { struct str_vector v; str_vector_init (&v); - split_str_ignore_empty (cut_word (&arguments), ' ', &v); + split_str_ignore_empty (cut_word (&a->arguments), ' ', &v); for (size_t i = 0; i < v.len; i++) { - if (*arguments) - irc_send (s, "PART %s :%s", v.vector[i], arguments); + // TODO: move this out + if (*a->arguments) + irc_send (a->s, "PART %s :%s", v.vector[i], a->arguments); else - irc_send (s, "PART %s", v.vector[i]); + irc_send (a->s, "PART %s", v.vector[i]); } str_vector_free (&v); } - else if (ctx->current_buffer->type != BUFFER_CHANNEL) - buffer_send_error (ctx, ctx->current_buffer, + else if (a->buffer->type != BUFFER_CHANNEL) + buffer_send_error (ctx, a->buffer, "%s: %s", "Can't part", "no channel name given and this buffer is not a channel"); // TODO: have a better way of checking if we're on the channel - else if (!ctx->current_buffer->channel->users) - buffer_send_error (ctx, ctx->current_buffer, + else if (!a->buffer->channel->users) + buffer_send_error (ctx, a->buffer, "%s: %s", "Can't part", "you're not on the channel"); - else if (*arguments) - irc_send (s, "PART %s :%s", - ctx->current_buffer->channel->name, arguments); + else if (*a->arguments) + irc_send (a->s, "PART %s :%s", a->buffer->channel->name, a->arguments); else - irc_send (s, "PART %s", ctx->current_buffer->channel->name); + irc_send (a->s, "PART %s", a->buffer->channel->name); return true; } @@ -6255,31 +6245,115 @@ cycle_channel (struct server *s, const char *channel_name, const char *reason) } static bool -handle_command_cycle (struct app_context *ctx, char *arguments) +handle_command_cycle (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("cycle", true) - - if (irc_is_channel (s, arguments)) + if (irc_is_channel (a->s, a->arguments)) { struct str_vector v; str_vector_init (&v); - split_str_ignore_empty (cut_word (&arguments), ' ', &v); + split_str_ignore_empty (cut_word (&a->arguments), ' ', &v); for (size_t i = 0; i < v.len; i++) - cycle_channel (s, v.vector[i], *arguments ? arguments : NULL); + // TODO: just send the arguments, also elsewhere + cycle_channel (a->s, v.vector[i], + *a->arguments ? a->arguments : NULL); str_vector_free (&v); } - else if (ctx->current_buffer->type != BUFFER_CHANNEL) - buffer_send_error (ctx, ctx->current_buffer, + else if (a->buffer->type != BUFFER_CHANNEL) + buffer_send_error (ctx, a->buffer, "%s: %s", "Can't cycle", "no channel name given and this buffer is not a channel"); // TODO: have a better way of checking if we're on the channel - else if (!ctx->current_buffer->channel->users) - buffer_send_error (ctx, ctx->current_buffer, + else if (!a->buffer->channel->users) + buffer_send_error (ctx, a->buffer, "%s: %s", "Can't cycle", "you're not on the channel"); - else if (*arguments) - cycle_channel (s, ctx->current_buffer->channel->name, arguments); + else if (*a->arguments) + cycle_channel (a->s, a->buffer->channel->name, a->arguments); else - cycle_channel (s, ctx->current_buffer->channel->name, NULL); + cycle_channel (a->s, a->buffer->channel->name, NULL); + return true; +} + +static bool +handle_command_mode (struct app_context *ctx, struct handler_args *a) +{ + // Channel names prefixed by "+" collide with mode strings, + // so we just disallow specifying these channels + char *target = NULL; + if (strchr ("+-\0", *a->arguments)) + { + if (a->buffer->type == BUFFER_CHANNEL) + target = a->buffer->channel->name; + if (a->buffer->type == BUFFER_PM) + target = a->buffer->user->nickname; + if (a->buffer->type == BUFFER_SERVER) + target = a->s->irc_user->nickname; + } + else + // If there a->arguments and they don't begin with a mode string, + // they're either a user name or a channel name + target = cut_word (&a->arguments); + + if (!target) + buffer_send_error (ctx, a->buffer, + "%s: %s", "Can't change mode", + "no target given and this buffer is neither a PM nor a channel"); + else if (*a->arguments) + // XXX: split channel mode params as necessary using irc_max_modes? + irc_send (a->s, "MODE %s %s", target, a->arguments); + else + irc_send (a->s, "MODE %s", target); + return true; +} + +static bool +handle_command_topic (struct app_context *ctx, struct handler_args *a) +{ + (void) ctx; + + if (*a->arguments) + // FIXME: there's no way to unset the topic + irc_send (a->s, "TOPIC %s :%s", a->channel_name, a->arguments); + else + irc_send (a->s, "TOPIC %s", a->channel_name); + return true; +} + +static bool +handle_command_kick (struct app_context *ctx, struct handler_args *a) +{ + (void) ctx; + + if (!*a->arguments) + return false; + + char *target = cut_word (&a->arguments); + if (*a->arguments) + irc_send (a->s, "KICK %s %s :%s", + a->channel_name, target, a->arguments); + else + irc_send (a->s, "KICK %s %s", a->channel_name, target); + return true; +} + +static bool +handle_command_kickban (struct app_context *ctx, struct handler_args *a) +{ + (void) ctx; + + if (!*a->arguments) + return false; + + char *target = cut_word (&a->arguments); + if (strpbrk (target, "!@*?")) + return false; + + // XXX: how about other masks? + irc_send (a->s, "MODE %s +b %s!*@*", a->channel_name, target); + if (*a->arguments) + irc_send (a->s, "KICK %s %s :%s", + a->channel_name, target, a->arguments); + else + irc_send (a->s, "KICK %s %s", a->channel_name, target); return true; } @@ -6308,126 +6382,13 @@ mass_channel_mode (struct server *s, const char *channel_name, } } -static bool -handle_command_channel_mode (struct app_context *ctx, char *arguments, - const char *command_name, bool adding, char mode_char) -{ - CHANNEL_COMMAND (command_name, true) - - if (!*arguments) - return false; - - struct str_vector v; - str_vector_init (&v); - split_str_ignore_empty (arguments, ' ', &v); - mass_channel_mode (s, channel_name, adding, mode_char, &v); - str_vector_free (&v); - return true; -} - -#define CHANMODE_HANDLER(name, adding, mode_char) \ - static bool \ - handle_command_ ## name (struct app_context *ctx, char *arguments) \ - { \ - return handle_command_channel_mode \ - (ctx, arguments, (#name), (adding), (mode_char)); \ - } - -CHANMODE_HANDLER (op, true, 'o') CHANMODE_HANDLER (deop, false, 'o') -CHANMODE_HANDLER (voice, true, 'v') CHANMODE_HANDLER (devoice, false, 'v') - -static bool -handle_command_mode (struct app_context *ctx, char *arguments) -{ - SERVER_COMMAND ("mode", true) - - // Channel names prefixed by "+" collide with mode strings, - // so we just disallow specifying these channels - char *target = NULL; - if (strchr ("+-\0", *arguments)) - { - if (ctx->current_buffer->type == BUFFER_CHANNEL) - target = ctx->current_buffer->channel->name; - if (ctx->current_buffer->type == BUFFER_PM) - target = ctx->current_buffer->user->nickname; - if (ctx->current_buffer->type == BUFFER_SERVER) - target = s->irc_user->nickname; - } - else - // If there arguments and they don't begin with a mode string, - // they're either a user name or a channel name - target = cut_word (&arguments); - - if (!target) - buffer_send_error (ctx, ctx->current_buffer, - "%s: %s", "Can't change mode", - "no target given and this buffer is neither a PM nor a channel"); - else if (*arguments) - // XXX: split channel mode params as necessary using irc_max_modes? - irc_send (s, "MODE %s %s", target, arguments); - else - irc_send (s, "MODE %s", target); - return true; -} - -static bool -handle_command_topic (struct app_context *ctx, char *arguments) -{ - // FIXME: we want "change topic" in the other error message - CHANNEL_COMMAND ("topic", true) - - if (*arguments) - // FIXME: there's no way to unset the topic - irc_send (s, "TOPIC %s :%s", channel_name, arguments); - else - irc_send (s, "TOPIC %s", channel_name); - return true; -} - -static bool -handle_command_kick (struct app_context *ctx, char *arguments) -{ - CHANNEL_COMMAND ("kick", true) - - if (!*arguments) - return false; - - char *target = cut_word (&arguments); - if (*arguments) - irc_send (s, "KICK %s %s :%s", channel_name, target, arguments); - else - irc_send (s, "KICK %s %s", channel_name, target); - return true; -} - -static bool -handle_command_kickban (struct app_context *ctx, char *arguments) -{ - CHANNEL_COMMAND ("kickban", true) - - if (!*arguments) - return false; - - char *target = cut_word (&arguments); - if (strpbrk (target, "!@*?")) - return false; - - // XXX: how about other masks? - irc_send (s, "MODE %s +b %s!*@*", channel_name, target); - if (*arguments) - irc_send (s, "KICK %s %s :%s", channel_name, target, arguments); - else - irc_send (s, "KICK %s %s", channel_name, target); - return true; -} - static void -mass_channel_mode_mask_list (struct server *s, const char *channel_name, - bool adding, char mode_char, const char *arguments) +mass_channel_mode_mask_list + (struct handler_args *a, bool adding, char mode_char) { struct str_vector v; str_vector_init (&v); - split_str_ignore_empty (arguments, ' ', &v); + split_str_ignore_empty (a->arguments, ' ', &v); // XXX: this may be a bit too trivial; we could map also nicknames // to information from WHO polling or userhost-in-names @@ -6441,86 +6402,68 @@ mass_channel_mode_mask_list (struct server *s, const char *channel_name, free (target); } - mass_channel_mode (s, channel_name, adding, mode_char, &v); + mass_channel_mode (a->s, a->channel_name, adding, mode_char, &v); str_vector_free (&v); } static bool -handle_command_ban (struct app_context *ctx, char *arguments) +handle_command_ban (struct app_context *ctx, struct handler_args *a) { - CHANNEL_COMMAND ("ban", true) + (void) ctx; - if (!*arguments) - { - irc_send (s, "MODE %s +b", channel_name); - return true; - } - - mass_channel_mode_mask_list (s, channel_name, true, 'b', arguments); + if (*a->arguments) + mass_channel_mode_mask_list (a, true, 'b'); + else + irc_send (a->s, "MODE %s +b", a->channel_name); return true; } static bool -handle_command_unban (struct app_context *ctx, char *arguments) +handle_command_unban (struct app_context *ctx, struct handler_args *a) { - CHANNEL_COMMAND ("unban", true) + (void) ctx; - if (!*arguments) + if (*a->arguments) + mass_channel_mode_mask_list (a, false, 'b'); + else return false; - - mass_channel_mode_mask_list (s, channel_name, false, 'b', arguments); return true; } static bool -handle_command_invite (struct app_context *ctx, char *arguments) +handle_command_invite (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("invite", true) + (void) ctx; struct str_vector v; str_vector_init (&v); - split_str_ignore_empty (arguments, ' ', &v); + split_str_ignore_empty (a->arguments, ' ', &v); - char *channel_name = NULL; - size_t last = v.len - 1; - if (v.len && irc_is_channel (s, v.vector[last])) - channel_name = str_vector_steal (&v, last); - else if (ctx->current_buffer->type == BUFFER_CHANNEL) - channel_name = xstrdup (ctx->current_buffer->channel->name); - - bool result = true; - if (!channel_name) - buffer_send_error (ctx, ctx->current_buffer, - "Can't %s: %s", "invite", - "no channel name given and this buffer is not a channel"); - else if (v.len) - for (size_t i = 0; i < v.len; i++) - irc_send (s, "INVITE %s %s", v.vector[i], channel_name); - else - result = false; + bool result = !!v.len; + for (size_t i = 0; i < v.len; i++) + irc_send (a->s, "INVITE %s %s", v.vector[i], a->channel_name); str_vector_free (&v); - free (channel_name); return result; } static bool -handle_command_connect (struct app_context *ctx, char *arguments) +handle_command_connect (struct app_context *ctx, struct handler_args *a) { struct server *s = NULL; - if (*arguments) + if (*a->arguments) { - char *server_name = cut_word (&arguments); + char *server_name = cut_word (&a->arguments); if (!(s = str_map_find (&ctx->servers, server_name))) buffer_send_error (ctx, ctx->global_buffer, "%s: %s: %s", "Can't connect", "no such server", server_name); } - else if (ctx->current_buffer->type == BUFFER_GLOBAL) - buffer_send_error (ctx, ctx->current_buffer, + else if (a->buffer->type == BUFFER_GLOBAL) + buffer_send_error (ctx, a->buffer, "%s: %s", "Can't connect", "no server name given and this buffer is global"); else - s = ctx->current_buffer->server; + s = a->buffer->server; if (!s) return true; @@ -6539,21 +6482,22 @@ handle_command_connect (struct app_context *ctx, char *arguments) } static bool -handle_command_disconnect (struct app_context *ctx, char *arguments) +handle_command_disconnect (struct app_context *ctx, struct handler_args *a) { struct server *s = NULL; - if (*arguments) + if (*a->arguments) { - char *server_name = cut_word (&arguments); + char *server_name = cut_word (&a->arguments); if (!(s = str_map_find (&ctx->servers, server_name))) - buffer_send_error (ctx, ctx->current_buffer, "%s: %s: %s", + buffer_send_error (ctx, a->buffer, "%s: %s: %s", "Can't disconnect", "no such server", server_name); } - else if (ctx->current_buffer->type == BUFFER_GLOBAL) - buffer_send_error (ctx, ctx->current_buffer, - "%s: %s", "Can't disconnect", "this buffer is global"); + else if (a->buffer->type == BUFFER_GLOBAL) + buffer_send_error (ctx, a->buffer, + "%s: %s", "Can't disconnect", + "no server name given and this buffer is global"); else - s = ctx->current_buffer->server; + s = a->buffer->server; if (!s) return true; @@ -6566,260 +6510,247 @@ handle_command_disconnect (struct app_context *ctx, char *arguments) else if (!irc_is_connected (s)) buffer_send_error (ctx, s->buffer, "Not connected"); else - irc_initiate_disconnect (s, *arguments ? arguments : NULL); + irc_initiate_disconnect (s, *a->arguments ? a->arguments : NULL); return true; } static bool -handle_command_list (struct app_context *ctx, char *arguments) +handle_command_names (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("list channels", true) + (void) ctx; - if (*arguments) - irc_send (s, "LIST %s", arguments); + char *channel_name = try_get_channel (a, maybe_cut_word); + if (channel_name) + irc_send (a->s, "NAMES %s", channel_name); else - irc_send (s, "LIST"); + irc_send (a->s, "NAMES"); return true; } static bool -handle_command_names (struct app_context *ctx, char *arguments) +handle_command_whois (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("names", true) - - char *channel_name = try_get_channel (ctx, &arguments); - if (!channel_name) - irc_send (s, "NAMES"); + if (*a->arguments) + irc_send (a->s, "WHOIS %s", a->arguments); + else if (a->buffer->type == BUFFER_PM) + irc_send (a->s, "WHOIS %s", a->buffer->user->nickname); + else if (a->buffer->type == BUFFER_SERVER) + irc_send (a->s, "WHOIS %s", a->s->irc_user->nickname); else - irc_send (s, "NAMES %s", channel_name); - return true; -} - -static bool -handle_command_who (struct app_context *ctx, char *arguments) -{ - SERVER_COMMAND ("who", true) - - if (*arguments) - irc_send (s, "WHO %s", arguments); - else - irc_send (s, "WHO"); - return true; -} - -static bool -handle_command_whois (struct app_context *ctx, char *arguments) -{ - SERVER_COMMAND ("whois", true) - - if (*arguments) - irc_send (s, "WHOIS %s", arguments); - else if (ctx->current_buffer->type == BUFFER_PM) - irc_send (s, "WHOIS %s", ctx->current_buffer->user->nickname); - else if (ctx->current_buffer->type == BUFFER_SERVER) - irc_send (s, "WHOIS %s", s->irc_user->nickname); - else - buffer_send_error (ctx, ctx->current_buffer, + buffer_send_error (ctx, a->buffer, "%s: %s", "Can't request info", "no target given and this buffer is not a PM nor a server"); return true; } static bool -handle_command_whowas (struct app_context *ctx, char *arguments) +handle_command_whowas (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("whowas", true) - - if (*arguments) - irc_send (s, "WHOWAS %s", arguments); - else if (ctx->current_buffer->type == BUFFER_PM) - irc_send (s, "WHOWAS %s", ctx->current_buffer->user->nickname); + if (*a->arguments) + irc_send (a->s, "WHOWAS %s", a->arguments); + else if (a->buffer->type == BUFFER_PM) + irc_send (a->s, "WHOWAS %s", a->buffer->user->nickname); else - buffer_send_error (ctx, ctx->current_buffer, + buffer_send_error (ctx, a->buffer, "%s: %s", "Can't request info", "no target given and this buffer is not a PM"); return true; } static bool -handle_command_motd (struct app_context *ctx, char *arguments) +handle_command_nick (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("motd", true) + (void) ctx; - if (*arguments) - irc_send (s, "MOTD %s", arguments); - else - irc_send (s, "MOTD"); - return true; -} - -static bool -handle_command_stats (struct app_context *ctx, char *arguments) -{ - SERVER_COMMAND ("stats", true) - - if (*arguments) - irc_send (s, "STATS %s", arguments); - else - irc_send (s, "STATS"); - return true; -} - -static bool -handle_command_away (struct app_context *ctx, char *arguments) -{ - SERVER_COMMAND ("away", true) - - if (*arguments) - irc_send (s, "AWAY %s", arguments); - else - irc_send (s, "AWAY"); - return true; -} - -static bool -handle_command_nick (struct app_context *ctx, char *arguments) -{ - SERVER_COMMAND ("change nickname", false) - - if (!*arguments) + if (!*a->arguments) return false; - irc_send (s, "NICK %s", cut_word (&arguments)); + irc_send (a->s, "NICK %s", cut_word (&a->arguments)); return true; } static bool -handle_command_quote (struct app_context *ctx, char *arguments) +handle_command_quote (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("quote", false) - irc_send (s, "%s", arguments); + (void) ctx; + irc_send (a->s, "%s", a->arguments); return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static bool handle_command_help (struct app_context *, char *); +static bool +handle_command_channel_mode + (struct handler_args *a, bool adding, char mode_char) +{ + if (!*a->arguments) + return false; + + struct str_vector v; + str_vector_init (&v); + split_str_ignore_empty (a->arguments, ' ', &v); + mass_channel_mode (a->s, a->channel_name, adding, mode_char, &v); + str_vector_free (&v); + return true; +} + +#define CHANMODE_HANDLER(name, adding, mode_char) \ + static bool \ + handle_command_ ## name (struct app_context *ctx, struct handler_args *a) \ + { \ + (void) ctx; \ + return handle_command_channel_mode (a, (adding), (mode_char)); \ + } + +CHANMODE_HANDLER (op, true, 'o') CHANMODE_HANDLER (deop, false, 'o') +CHANMODE_HANDLER (voice, true, 'v') CHANMODE_HANDLER (devoice, false, 'v') + +#define TRIVIAL_HANDLER(name, command) \ + static bool \ + handle_command_ ## name (struct app_context *ctx, struct handler_args *a) \ + { \ + (void) ctx; \ + if (*a->arguments) \ + irc_send (a->s, #command " %s", a->arguments); \ + else \ + irc_send (a->s, #command); \ + return true; \ + } + +TRIVIAL_HANDLER (list, "LIST") +TRIVIAL_HANDLER (who, "WHO") +TRIVIAL_HANDLER (motd, "MOTD") +TRIVIAL_HANDLER (stats, "STATS") +TRIVIAL_HANDLER (away, "AWAY") + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static bool handle_command_help (struct app_context *, struct handler_args *); static struct command_handler { const char *name; const char *description; const char *usage; - bool (*handler) (struct app_context *ctx, char *arguments); + bool (*handler) (struct app_context *ctx, struct handler_args *a); + enum handler_flags flags; } g_command_handlers[] = { { "help", "Show help", "[ |