/* modes.c : irssi Copyright (C) 1999-2000 Timo Sirainen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "module.h" #include "commands.h" #include "signals.h" #include "irc.h" #include "mode-lists.h" #include "nicklist.h" /* Change nick's mode in channel */ static void nick_mode_change(CHANNEL_REC *channel, const char *nick, const char mode, gboolean set) { NICK_REC *nickrec; g_return_if_fail(channel != NULL); g_return_if_fail(nick != NULL); nickrec = nicklist_find(channel, nick); if (nickrec == NULL) return; /* No /names list got yet */ if (mode == '@') nickrec->op = set; if (mode == '+') nickrec->voice = set; signal_emit("nick mode changed", 2, channel, nickrec); } /* Parse channel mode string */ void parse_channel_modes(CHANNEL_REC *channel, const char *setby, const char *mode) { char *dup, *modestr, *ptr, *curmode, type; g_return_if_fail(channel != NULL); g_return_if_fail(setby != NULL); g_return_if_fail(modestr != NULL); type = '+'; dup = modestr = g_strdup(mode); curmode = cmd_get_param(&modestr); while (*curmode != '\0') { switch (*curmode) { case '+': case '-': type = *curmode; break; case 'b': ptr = cmd_get_param(&modestr); if (*ptr == '\0') break; if (type == '+') banlist_add(channel, ptr, setby, time(NULL)); else banlist_remove(channel, ptr); break; case 'e': ptr = cmd_get_param(&modestr); if (*ptr == '\0') break; if (type == '+') banlist_exception_add(channel, ptr, setby, time(NULL)); else banlist_exception_remove(channel, ptr); break; case 'I': ptr = cmd_get_param(&modestr); if (*ptr == '\0') break; if (type == '+') invitelist_add(channel, ptr); else invitelist_remove(channel, ptr); break; case 'v': ptr = cmd_get_param(&modestr); if (*ptr != '\0') nick_mode_change(channel, ptr, '+', type == '+'); break; case 'o': ptr = cmd_get_param(&modestr); if (*ptr == '\0') break; if (strcmp(channel->server->nick, ptr) == 0) channel->chanop = type == '+' ? TRUE : FALSE; nick_mode_change(channel, ptr, '@', type == '+'); break; case 'l': if (type == '-') channel->limit = 0; else { ptr = cmd_get_param(&modestr); sscanf(ptr, "%d", &channel->limit); } signal_emit("channel mode changed", 1, channel); break; case 'k': ptr = cmd_get_param(&modestr); if (*ptr != '\0' || type == '-') { g_free_and_null(channel->key); if (type == '+') { channel->key = g_strdup(ptr); channel->mode_key = TRUE; } } signal_emit("channel mode changed", 1, channel); break; default: switch (*curmode) { case 'i': channel->mode_invite = type == '+'; break; case 'm': channel->mode_moderate = type == '+'; break; case 's': channel->mode_secret = type == '+'; break; case 'p': channel->mode_private = type == '+'; break; case 'n': channel->mode_nomsgs = type == '+'; break; case 't': channel->mode_optopic = type == '+'; break; case 'a': channel->mode_anonymous = type == '+'; break; case 'r': channel->mode_reop = type == '+'; break; } signal_emit("channel mode changed", 1, channel); break; } curmode++; } g_free(dup); if (!channel->mode_key && channel->key != NULL) { /* join was used with key but there's no key set in channel modes.. */ g_free(channel->key); channel->key = NULL; } } static int compare_char(const void *p1, const void *p2) { const char *c1 = p1, *c2 = p2; return *c1 < *c2 ? -1 : (*c1 > *c2 ? 1 : 0); } /* add `mode' to `old' - return newly allocated mode. */ char *modes_join(const char *old, const char *mode) { GString *newmode; char type, *p; g_return_val_if_fail(mode != NULL, NULL); type = '+'; newmode = g_string_new(old); while (*mode != '\0' && *mode != ' ') { if (*mode == '+' || *mode == '-') { type = *mode; } else { p = strchr(newmode->str, *mode); if (type == '+' && p == NULL) g_string_append_c(newmode, *mode); else if (type == '-' && p != NULL) g_string_erase(newmode, (int) (p-newmode->str), 1); } mode++; } qsort(newmode->str, sizeof(char), newmode->len, compare_char); p = newmode->str; g_string_free(newmode, FALSE); return p; } /* Parse user mode string */ static void parse_user_mode(IRC_SERVER_REC *server, const char *modestr) { char *newmode, *oldmode; g_return_if_fail(server != NULL); g_return_if_fail(modestr != NULL); newmode = modes_join(server->usermode, modestr); oldmode = server->usermode; server->usermode = newmode; server->server_operator = (strchr(newmode, 'o') != NULL); signal_emit("user mode changed", 2, server, oldmode); g_free_not_null(oldmode); } static void event_user_mode(const char *data, IRC_SERVER_REC *server) { char *params, *nick, *mode; g_return_if_fail(data != NULL); params = event_get_params(data, 3, NULL, &nick, &mode); parse_user_mode(server, mode); g_free(params); } static void event_mode(const char *data, IRC_SERVER_REC *server, const char *nick) { CHANNEL_REC *chanrec; char *params, *channel, *mode; g_return_if_fail(data != NULL); params = event_get_params(data, 2 | PARAM_FLAG_GETREST, &channel, &mode); if (!ischannel(*channel)) { /* user mode change */ parse_user_mode(server, mode); } else { /* channel mode change */ chanrec = channel_find(server, channel); if (chanrec != NULL) parse_channel_modes(chanrec, nick, mode); } g_free(params); } static void event_away(const char *data, IRC_SERVER_REC *server) { g_return_if_fail(server != NULL); server->usermode_away = TRUE; signal_emit("away mode changed", 1, server); } static void event_unaway(const char *data, IRC_SERVER_REC *server) { g_return_if_fail(server != NULL); server->usermode_away = FALSE; g_free_and_null(server->away_reason); signal_emit("away mode changed", 1, server); } void channel_set_singlemode(IRC_SERVER_REC *server, const char *channel, const char *nicks, const char *mode) { GString *str; int num, modepos; char **nick, **nicklist; g_return_if_fail(server != NULL); g_return_if_fail(channel != NULL); g_return_if_fail(nicks != NULL); g_return_if_fail(mode != NULL); if (*nicks == '\0') return; num = modepos = 0; str = g_string_new(NULL); nicklist = g_strsplit(nicks, " ", -1); for (nick = nicklist; *nick != NULL; nick++) { if (num == 0) { g_string_sprintf(str, "MODE %s %s", channel, mode); modepos = str->len; } else { /* insert the mode string */ g_string_insert(str, modepos, mode); } g_string_sprintfa(str, " %s", *nick); if (++num == server->connrec->max_modes) { /* max. modes / command reached, send to server */ irc_send_cmd(server, str->str); num = 0; } } if (num > 0) irc_send_cmd(server, str->str); g_strfreev(nicklist); g_string_free(str, TRUE); } #define MODE_HAS_ARG(c) ((c) == 'b' || (c) == 'e' || (c) == 'I' || \ (c) == 'v' || (c) == 'o' || (c) == 'l' || (c) == 'k') void channel_set_mode(IRC_SERVER_REC *server, const char *channel, const char *mode) { char *modestr, *curmode, type, *orig; GString *tmode, *targs; int count; g_return_if_fail(server != NULL); g_return_if_fail(channel != NULL); g_return_if_fail(mode != NULL); tmode = g_string_new(NULL); targs = g_string_new(NULL); type = '+'; count = 0; orig = modestr = g_strdup(mode); curmode = cmd_get_param(&modestr); for (; *curmode != '\0'; curmode++) { if (*curmode == '+' || *curmode == '-') { type = *curmode; continue; } if (count == server->connrec->max_modes && MODE_HAS_ARG(*curmode)) { irc_send_cmdv(server, "MODE %s %s%s", channel, tmode->str, targs->str); count = 0; g_string_truncate(tmode, 0); g_string_truncate(targs, 0); } g_string_append_c(tmode, *curmode); if (MODE_HAS_ARG(*curmode)) { char *arg; count++; arg = cmd_get_param(&modestr); if (*arg != '\0') g_string_sprintfa(targs, " %s", arg); } } if (tmode->len > 0) irc_send_cmdv(server, "MODE %s %s%s", channel, tmode->str, targs->str); g_string_free(tmode, TRUE); g_string_free(targs, TRUE); g_free(orig); } static void cmd_op(gchar *data, IRC_SERVER_REC *server, WI_IRC_REC *item) { if (!irc_item_channel(item)) return; channel_set_singlemode(server, item->name, data, "+o"); } static void cmd_deop(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) { if (!irc_item_channel(item)) return; channel_set_singlemode(server, item->name, data, "-o"); } static void cmd_voice(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) { if (!irc_item_channel(item)) return; channel_set_singlemode(server, item->name, data, "+v"); } static void cmd_devoice(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) { if (!irc_item_channel(item)) return; channel_set_singlemode(server, item->name, data, "-v"); } static void cmd_mode(const char *data, IRC_SERVER_REC *server, WI_IRC_REC *item) { char *params, *target, *mode; g_return_if_fail(data != NULL); if (server == NULL || !server->connected || !irc_server_check(server)) cmd_return_error(CMDERR_NOT_CONNECTED); params = cmd_get_params(data, 2 | PARAM_FLAG_GETREST, &target, &mode); if (strcmp(target, "*") == 0) { if (!irc_item_channel(item)) cmd_return_error(CMDERR_NOT_JOINED); target = item->name; } if (*target == '\0') cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS); if (ischannel(*target)) channel_set_mode(server, target, mode); else irc_send_cmdv(server, "MODE %s %s", target, mode); g_free(params); } void modes_init(void) { signal_add("event 221", (SIGNAL_FUNC) event_user_mode); signal_add("event 305", (SIGNAL_FUNC) event_unaway); signal_add("event 306", (SIGNAL_FUNC) event_away); signal_add("event mode", (SIGNAL_FUNC) event_mode); command_bind("op", NULL, (SIGNAL_FUNC) cmd_op); command_bind("deop", NULL, (SIGNAL_FUNC) cmd_deop); command_bind("voice", NULL, (SIGNAL_FUNC) cmd_voice); command_bind("devoice", NULL, (SIGNAL_FUNC) cmd_devoice); command_bind("mode", NULL, (SIGNAL_FUNC) cmd_mode); } void modes_deinit(void) { signal_remove("event 221", (SIGNAL_FUNC) event_user_mode); signal_remove("event 305", (SIGNAL_FUNC) event_unaway); signal_remove("event 306", (SIGNAL_FUNC) event_away); signal_remove("event mode", (SIGNAL_FUNC) event_mode); command_unbind("op", (SIGNAL_FUNC) cmd_op); command_unbind("deop", (SIGNAL_FUNC) cmd_deop); command_unbind("voice", (SIGNAL_FUNC) cmd_voice); command_unbind("devoice", (SIGNAL_FUNC) cmd_devoice); command_unbind("mode", (SIGNAL_FUNC) cmd_mode); }