diff options
Diffstat (limited to 'degesch.c')
-rw-r--r-- | degesch.c | 839 |
1 files changed, 397 insertions, 442 deletions
@@ -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) -{ - 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); +handle_command_me (struct app_context *ctx, struct handler_args *a) +{ + 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,179 +6245,150 @@ 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); - return true; -} - -static void -mass_channel_mode (struct server *s, const char *channel_name, - bool adding, char mode_char, struct str_vector *v) -{ - size_t n; - for (size_t i = 0; i < v->len; i += n) - { - struct str modes; str_init (&modes); - struct str params; str_init (¶ms); - - n = MIN (v->len - i, s->irc_max_modes); - str_append_printf (&modes, "MODE %s %c", channel_name, "-+"[adding]); - for (size_t k = 0; k < n; k++) - { - str_append_c (&modes, mode_char); - str_append_printf (¶ms, " %s", v->vector[i + k]); - } - - irc_send (s, "%s%s", modes.str, params.str); - - str_free (&modes); - str_free (¶ms); - } -} - -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); + cycle_channel (a->s, a->buffer->channel->name, NULL); 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) +handle_command_mode (struct app_context *ctx, struct handler_args *a) { - 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 (strchr ("+-\0", *a->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; + 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 arguments and they don't begin with a mode string, + // 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 (&arguments); + target = cut_word (&a->arguments); if (!target) - buffer_send_error (ctx, ctx->current_buffer, + 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 (*arguments) + else if (*a->arguments) // XXX: split channel mode params as necessary using irc_max_modes? - irc_send (s, "MODE %s %s", target, arguments); + irc_send (a->s, "MODE %s %s", target, a->arguments); else - irc_send (s, "MODE %s", target); + irc_send (a->s, "MODE %s", target); return true; } static bool -handle_command_topic (struct app_context *ctx, char *arguments) +handle_command_topic (struct app_context *ctx, struct handler_args *a) { - // FIXME: we want "change topic" in the other error message - CHANNEL_COMMAND ("topic", true) + (void) ctx; - if (*arguments) + if (*a->arguments) // FIXME: there's no way to unset the topic - irc_send (s, "TOPIC %s :%s", channel_name, arguments); + irc_send (a->s, "TOPIC %s :%s", a->channel_name, a->arguments); else - irc_send (s, "TOPIC %s", channel_name); + irc_send (a->s, "TOPIC %s", a->channel_name); return true; } static bool -handle_command_kick (struct app_context *ctx, char *arguments) +handle_command_kick (struct app_context *ctx, struct handler_args *a) { - CHANNEL_COMMAND ("kick", true) + (void) ctx; - if (!*arguments) + if (!*a->arguments) return false; - char *target = cut_word (&arguments); - if (*arguments) - irc_send (s, "KICK %s %s :%s", channel_name, target, arguments); + 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 (s, "KICK %s %s", channel_name, target); + irc_send (a->s, "KICK %s %s", a->channel_name, target); return true; } static bool -handle_command_kickban (struct app_context *ctx, char *arguments) +handle_command_kickban (struct app_context *ctx, struct handler_args *a) { - CHANNEL_COMMAND ("kickban", true) + (void) ctx; - if (!*arguments) + if (!*a->arguments) return false; - char *target = cut_word (&arguments); + char *target = cut_word (&a->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); + 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 (s, "KICK %s %s", channel_name, target); + irc_send (a->s, "KICK %s %s", a->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 (struct server *s, const char *channel_name, + bool adding, char mode_char, struct str_vector *v) +{ + size_t n; + for (size_t i = 0; i < v->len; i += n) + { + struct str modes; str_init (&modes); + struct str params; str_init (¶ms); + + n = MIN (v->len - i, s->irc_max_modes); + str_append_printf (&modes, "MODE %s %c", channel_name, "-+"[adding]); + for (size_t k = 0; k < n; k++) + { + str_append_c (&modes, mode_char); + str_append_printf (¶ms, " %s", v->vector[i + k]); + } + + irc_send (s, "%s%s", modes.str, params.str); + + str_free (&modes); + str_free (¶ms); + } +} + +static void +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) - - if (!*arguments) - { - irc_send (s, "MODE %s +b", channel_name); - return true; - } + (void) ctx; - 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); - - 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); + split_str_ignore_empty (a->arguments, ' ', &v); - 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) - - if (*arguments) - irc_send (s, "LIST %s", arguments); - else - irc_send (s, "LIST"); - return true; -} - -static bool -handle_command_names (struct app_context *ctx, char *arguments) -{ - SERVER_COMMAND ("names", true) - - char *channel_name = try_get_channel (ctx, &arguments); - if (!channel_name) - irc_send (s, "NAMES"); - 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) + (void) ctx; - if (*arguments) - irc_send (s, "WHO %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, "WHO"); + irc_send (a->s, "NAMES"); 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); +handle_command_whois (struct app_context *ctx, struct handler_args *a) +{ + 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 - 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"); + if (!*a->arguments) + return false; + + irc_send (a->s, "NICK %s", cut_word (&a->arguments)); return true; } static bool -handle_command_stats (struct app_context *ctx, char *arguments) +handle_command_quote (struct app_context *ctx, struct handler_args *a) { - SERVER_COMMAND ("stats", true) - - if (*arguments) - irc_send (s, "STATS %s", arguments); - else - irc_send (s, "STATS"); + (void) ctx; + irc_send (a->s, "%s", a->arguments); return true; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + static bool -handle_command_away (struct app_context *ctx, char *arguments) +handle_command_channel_mode + (struct handler_args *a, bool adding, char mode_char) { - SERVER_COMMAND ("away", true) + if (!*a->arguments) + return false; - if (*arguments) - irc_send (s, "AWAY %s", arguments); - else - irc_send (s, "AWAY"); + 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; } -static bool -handle_command_nick (struct app_context *ctx, char *arguments) -{ - SERVER_COMMAND ("change nickname", false) +#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)); \ + } - if (!*arguments) - return false; +CHANMODE_HANDLER (op, true, 'o') CHANMODE_HANDLER (deop, false, 'o') +CHANMODE_HANDLER (voice, true, 'v') CHANMODE_HANDLER (devoice, false, 'v') - irc_send (s, "NICK %s", cut_word (&arguments)); - return true; -} +#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; \ + } -static bool -handle_command_quote (struct app_context *ctx, char *arguments) -{ - SERVER_COMMAND ("quote", false) - irc_send (s, "%s", arguments); - 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 *, char *); +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", "[<command> | <option>]", - handle_command_help }, + handle_command_help, 0 }, { "quit", "Quit the program", "[<message>]", - handle_command_quit }, + handle_command_quit, 0 }, { "buffer", "Manage buffers", "list | clear | move | { close [<number> | <name>] } | <number>", - handle_command_buffer }, + handle_command_buffer, 0 }, { "set", "Manage configuration", "[<option>]", - handle_command_set }, + handle_command_set, 0 }, { "save", "Save configuration", NULL, - handle_command_save }, + handle_command_save, 0 }, { "msg", "Send message to a nick or channel", "<target> <message>", - handle_command_msg }, + handle_command_msg, HANDLER_SERVER | HANDLER_NEEDS_REG }, { "query", "Send a private message to a nick", "<nick> <message>", - handle_command_query }, + handle_command_query, HANDLER_SERVER | HANDLER_NEEDS_REG }, { "notice", "Send notice to a nick or channel", "<target> <message>", - handle_command_notice }, + handle_command_notice, HANDLER_SERVER | HANDLER_NEEDS_REG }, { "ctcp", "Send a CTCP query", "<target> <tag>", - handle_command_ctcp }, + handle_command_ctcp, HANDLER_SERVER | HANDLER_NEEDS_REG }, { "me", "Send a CTCP action", "<message>", - handle_command_me }, + handle_command_me, HANDLER_SERVER | HANDLER_NEEDS_REG }, { "join", "Join channels", "[<channel>[,<channel>...]] [<key>[,<key>...]]", - handle_command_join }, + handle_command_join, HANDLER_SERVER }, { "part", "Leave channels", "[<channel>[,<channel>...]] [<reason>]", - handle_command_part }, + handle_command_part, HANDLER_SERVER }, { "cycle", "Rejoin channels", "[<channel>[,<channel>...]] [<reason>]", - handle_command_cycle }, + handle_command_cycle, HANDLER_SERVER }, { "op", "Give channel operator status", - "<nick>...", handle_command_op }, + "<nick>...", + handle_command_op, HANDLER_SERVER | HANDLER_CHANNEL_FIRST }, { "deop", "Remove channel operator status", - "<nick>...", handle_command_deop }, + "<nick>...", + handle_command_deop, HANDLER_SERVER | HANDLER_CHANNEL_FIRST }, { "voice", "Give voice", - "<nick>...", handle_command_voice }, + "<nick>...", + handle_command_voice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST }, { "devoice", "Remove voice", - "<nick>...", handle_command_devoice }, + "<nick>...", + handle_command_devoice, HANDLER_SERVER | HANDLER_CHANNEL_FIRST }, { "mode", "Change mode", "[<channel>] [<mode>...]", - handle_command_mode }, + handle_command_mode, HANDLER_SERVER }, { "topic", "Change topic", "[<channel>] [<topic>]", - handle_command_topic }, + handle_command_topic, HANDLER_SERVER | HANDLER_CHANNEL_FIRST }, { "kick", "Kick user from channel", "[<channel>] <user> [<reason>]", - handle_command_kick }, + handle_command_kick, HANDLER_SERVER | HANDLER_CHANNEL_FIRST }, { "kickban", "Kick and ban user from channel", "[<channel>] <user> [<reason>]", - handle_command_kickban }, + handle_command_kickban, HANDLER_SERVER | HANDLER_CHANNEL_FIRST }, { "ban", "Ban user from channel", "[<channel>] [<mask>...]", - handle_command_ban }, + handle_command_ban, HANDLER_SERVER | HANDLER_CHANNEL_FIRST }, { "unban", "Unban user from channel", "[<channel>] <mask>...", - handle_command_unban }, + handle_command_unban, HANDLER_SERVER | HANDLER_CHANNEL_FIRST }, { "invite", "Invite user to channel", "<user>... [<channel>]", - handle_command_invite }, + handle_command_invite, HANDLER_SERVER | HANDLER_CHANNEL_LAST }, { "connect", "Connect to the server", "[<server>]", - handle_command_connect }, + handle_command_connect, 0 }, { "disconnect", "Disconnect from the server", "[<server> [<reason>]]", - handle_command_disconnect }, + handle_command_disconnect, 0 }, { "list", "List channels and their topic", "[<channel>[,<channel>...]] [<target>]", - handle_command_list }, + handle_command_list, HANDLER_SERVER }, { "names", "List users on channel", "[<channel>[,<channel>...]]", - handle_command_names }, + handle_command_names, HANDLER_SERVER }, { "who", "List users", "[<mask> [o]]", - handle_command_who }, + handle_command_who, HANDLER_SERVER }, { "whois", "Get user information", "[<target>] <mask>", - handle_command_whois }, + handle_command_whois, HANDLER_SERVER }, { "whowas", "Get user information", "<user> [<count> [<target>]]", - handle_command_whowas }, + handle_command_whowas, HANDLER_SERVER }, { "motd", "Get the Message of The Day", "[<target>]", - handle_command_motd }, + handle_command_motd, HANDLER_SERVER }, { "stats", "Query server statistics", "[<query> [<target>]]", - handle_command_stats }, + handle_command_stats, HANDLER_SERVER }, { "away", "Set away status", "[<text>]", - handle_command_away }, + handle_command_away, HANDLER_SERVER }, { "nick", "Change current nick", "<nickname>", - handle_command_nick }, + handle_command_nick, HANDLER_SERVER }, { "quote", "Send a raw command to the server", "<command>", - handle_command_quote }, + handle_command_quote, HANDLER_SERVER }, }; static bool @@ -6858,9 +6789,9 @@ try_handle_command_help_option (struct app_context *ctx, const char *name) } static bool -handle_command_help (struct app_context *ctx, char *arguments) +handle_command_help (struct app_context *ctx, struct handler_args *a) { - if (!*arguments) + if (!*a->arguments) { buffer_send_status (ctx, ctx->global_buffer, "%s", ""); buffer_send_status (ctx, ctx->global_buffer, "Commands:"); @@ -6880,7 +6811,7 @@ handle_command_help (struct app_context *ctx, char *arguments) return true; } - char *command = cut_word (&arguments); + char *command = cut_word (&a->arguments); for (size_t i = 0; i < N_ELEMENTS (g_command_handlers); i++) { struct command_handler *handler = &g_command_handlers[i]; @@ -6937,7 +6868,7 @@ init_partial_matching_user_command_map (struct str_map *partial) } static void -process_user_command (struct app_context *ctx, char *command) +process_user_command (struct app_context *ctx, char *input) { static bool initialized = false; static struct str_map partial; @@ -6947,15 +6878,39 @@ process_user_command (struct app_context *ctx, char *command) initialized = true; } - char *name = cut_word (&command); - if (try_handle_buffer_goto (ctx, name)) + char *command_name = cut_word (&input); + if (try_handle_buffer_goto (ctx, command_name)) return; - struct command_handler *handler = str_map_find (&partial, name); + struct handler_args args = + { + .buffer = ctx->current_buffer, + .arguments = input, + }; + + struct command_handler *handler = str_map_find (&partial, command_name); if (!handler) buffer_send_error (ctx, ctx->global_buffer, - "%s: %s", "No such command", name); - else if (!handler->handler (ctx, command)) + "%s: %s", "No such command", command_name); + else if ((handler->flags & HANDLER_SERVER) + && args.buffer->type == BUFFER_GLOBAL) + buffer_send_error (ctx, ctx->global_buffer, + "/%s: %s", command_name, "can't do this from a global buffer"); + else if ((handler->flags & HANDLER_SERVER) + && !irc_is_connected ((args.s = args.buffer->server))) + buffer_send_error (ctx, args.s->buffer, "Not connected"); + else if ((handler->flags & HANDLER_NEEDS_REG) + && args.s->state != IRC_REGISTERED) + buffer_send_error (ctx, args.s->buffer, "Not registered"); + else if (((handler->flags & HANDLER_CHANNEL_FIRST) + && !(args.channel_name = + try_get_channel (&args, maybe_cut_word))) + || ((handler->flags & HANDLER_CHANNEL_LAST) + && !(args.channel_name = + try_get_channel (&args, maybe_cut_word_from_end)))) + buffer_send_error (ctx, args.buffer, "/%s: %s", command_name, + "no channel name given and this buffer is not a channel"); + else if (!handler->handler (ctx, &args)) buffer_send_error (ctx, ctx->global_buffer, "%s: /%s %s", "Usage", handler->name, handler->usage); } |