def unquiet(bot, trigger): """Unquiet a user. The bot must be a channel operator for this command to work. """ chanops = get_chanops(bot, trigger) if bot.channels[trigger.sender].privileges[ bot.nick] < OP and trigger.account in chanops: bot.say('Please wait...') bot.say('op ' + trigger.sender, 'ChanServ') time.sleep(1) text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) quietmask = opt channel = trigger.sender if not opt.is_nick(): if argc < 3: return quietmask = text[2] channel = opt quietmask = configureHostMask(quietmask) if quietmask == '': return if trigger.account in chanops: bot.write(['MODE', channel, '-q', quietmask]) else: bot.reply( 'Access Denied. If in error, please contact the channel founder.')
def kick(bot, trigger): """Kick a user from the channel.""" chanops = get_chanops(bot, trigger) if bot.channels[trigger.sender].privileges[ bot.nick] < OP and trigger.nick in chanops: bot.say('doing...') bot.say('op ' + trigger.sender, 'ChanServ') time.sleep(1) text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) nick = opt channel = trigger.sender reasonidx = 2 if not opt.is_nick(): if argc < 3: return nick = text[2] channel = opt reasonidx = 3 reason = ' '.join(text[reasonidx:]) if nick != bot.config.core.nick and trigger.nick in chanops: bot.write(['KICK', channel, nick, ':' + reason]) else: bot.reply('Log in as a Channel Operator to change this setting')
def kickban(bot, trigger): """ This gives admins the ability to kickban a user. The bot must be a Channel Operator for this command to work. .kickban [#chan] user1 user!*@* get out of here """ if bot.privileges[trigger.sender][bot.nick] < HALFOP: return bot.reply("I'm not a channel operator!") text = trigger.group().split() argc = len(text) if argc < 4: return opt = Identifier(text[1]) nick = opt mask = text[2] reasonidx = 3 if not opt.is_nick(): if argc < 5: return channel = opt nick = text[2] mask = text[3] reasonidx = 4 reason = ' '.join(text[reasonidx:]) mask = configureHostMask(mask) if mask == '': return bot.write(['MODE', channel, '+b', mask]) bot.write(['KICK', channel, nick, ' :', reason])
def get_nick_or_channel_value(self, name, key, default=None): """Gets the value `key` associated to the nick or channel `name`.""" name = Identifier(name) if name.is_nick(): return self.get_nick_value(name, key, default) else: return self.get_channel_value(name, key, default)
def kick(bot, trigger): """Kick a user from the channel.""" chanops = get_chanops(bot, trigger) if bot.channels[trigger.sender].privileges[ bot.nick] < OP and trigger.account in chanops: bot.say('Please wait...') bot.say('op ' + trigger.sender, 'ChanServ') time.sleep(1) text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) nick = opt channel = trigger.sender reasonidx = 2 if not opt.is_nick(): if argc < 3: return nick = text[2] channel = opt reasonidx = 3 reason = ' '.join(text[reasonidx:]) if nick != bot.config.core.nick and trigger.account in chanops: bot.write(['KICK', channel, nick, ':' + reason]) else: bot.reply( 'Access Denied. If in error, please contact the channel founder.')
def get_nick_or_channel_value(self, name, key, default=None): """Get a value from the key-value store for ``name``. :param str name: nick or channel whose values to access :param str key: the name by which the desired value was saved :param mixed default: value to return if ``key`` does not have a value set (optional) :raise ~sqlalchemy.exc.SQLAlchemyError: if there is a database error .. versionadded:: 7.0 The ``default`` parameter. This is useful for common logic that is shared between both users and channels, as it will fetch the appropriate value based on what type of ``name`` it is given. .. seealso:: To get a value for a nick specifically, use :meth:`get_nick_value`. To get a value for a channel specifically, use :meth:`get_channel_value`. """ name = Identifier(name) if name.is_nick(): return self.get_nick_value(name, key, default) else: return self.get_channel_value(name, key, default)
def get_nick_or_channel_value(self, name, key): """Gets the value `key` associated to the nick or channel `name`.""" name = Identifier(name) if name.is_nick(): return self.get_nick_value(name, key) else: return self.get_channel_value(name, key)
def kickban(bot, trigger): """ This gives admins the ability to kickban a user. The bot must be a Channel Operator for this command to work. .kickban [#chan] user1 user!*@* get out of here """ if bot.privileges[trigger.sender][bot.nick] < HALFOP: return bot.reply("I'm not a channel operator!") text = trigger.group().split() argc = len(text) if argc < 4: return opt = Identifier(text[1]) nick = opt mask = text[2] channel = trigger.sender reasonidx = 3 if not opt.is_nick(): if argc < 5: return channel = opt nick = text[2] mask = text[3] reasonidx = 4 reason = ' '.join(text[reasonidx:]) mask = configureHostMask(mask) if mask == '': return bot.write(['MODE', channel, '+b', mask]) bot.write(['KICK', channel, nick], reason)
def kickban(bot, trigger): """Kick and ban a user from the channel The bot must be a channel operator for this command to work. """ if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP: return bot.reply("I'm not a channel operator!") text = trigger.group().split() argc = len(text) if argc < 4: return opt = Identifier(text[1]) nick = opt mask = text[2] channel = trigger.sender reasonidx = 3 if not opt.is_nick(): if argc < 5: return channel = opt nick = text[2] mask = text[3] reasonidx = 4 reason = ' '.join(text[reasonidx:]) mask = configureHostMask(mask) if mask == '': return bot.write(['MODE', channel, '+b', mask]) bot.write(['KICK', channel, nick], reason)
def unquiet(bot, trigger): """Unquiet a user. The bot must be a channel operator for this command to work. """ chanops = get_chanops(bot, trigger) if bot.channels[trigger.sender].privileges[ bot.nick] < OP and trigger.nick in chanops: bot.say('doing...') bot.say('op ' + trigger.sender, 'ChanServ') time.sleep(1) text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) quietmask = opt channel = trigger.sender if not opt.is_nick(): if argc < 3: return quietmask = text[2] channel = opt quietmask = configureHostMask(quietmask) if quietmask == '': return if trigger.nick in chanops: bot.write(['MODE', channel, '-q', quietmask]) else: bot.reply('Log in as a Channel Operator to change this setting')
def kickban(bot, trigger): """Kick and ban a user from the channel. The bot must be a channel operator for this command to work. """ chanops = get_chanops(bot, trigger) if bot.channels[trigger.sender].privileges[ bot.nick] < OP and trigger.nick in chanops: bot.say('doing...') bot.say('op ' + trigger.sender, 'ChanServ') time.sleep(1) text = trigger.group().split() argc = len(text) if argc < 3: return opt = Identifier(text[1]) nick = opt mask = text[2] if any([s in text[2] for s in "!@*"]) else '' channel = trigger.sender reasonidx = 3 if mask != '' else 2 if not opt.is_nick(): if argc < 5: return channel = opt nick = text[2] mask = text[3] if any([s in text[3] for s in "!@*"]) else '' reasonidx = 4 if mask != '' else 3 reason = ' '.join(text[reasonidx:]) mask = configureHostMask(mask) if mask == '': mask = nick + '!*@*' if trigger.nick in chanops: bot.write(['MODE', channel, '+b', mask]) bot.write(['KICK', channel, nick, ':' + reason]) else: bot.reply('Log in as a Channel Operator to change this setting')
def track_modes(bot, trigger): """Track usermode changes and keep our lists of ops up to date.""" # Mode message format: <channel> *( ( "-" / "+" ) *<modes> *<modeparams> ) channel = Identifier(trigger.args[0]) line = trigger.args[1:] # If the first character of where the mode is being set isn't a # # then it's a user mode, not a channel mode, so we'll ignore it. if channel.is_nick(): return mapping = { 'v': sopel.module.VOICE, 'h': sopel.module.HALFOP, 'o': sopel.module.OP, 'a': sopel.module.ADMIN, 'q': sopel.module.OWNER } modes = [] for arg in line: if len(arg) == 0: continue if arg[0] in '+-': # There was a comment claiming IRC allows e.g. MODE +aB-c foo, but # I don't see it in any RFCs. Leaving in the extra parsing for now. sign = '' modes = [] for char in arg: if char == '+' or char == '-': sign = char else: modes.append(sign + char) else: arg = Identifier(arg) for mode in modes: priv = bot.channels[channel].privileges.get(arg, 0) # Log a warning if the two privilege-tracking data structures # get out of sync. That should never happen. # This is a good place to verify that bot.channels is doing # what it's supposed to do before ultimately removing the old, # deprecated bot.privileges structure completely. ppriv = bot.privileges[channel].get(arg, 0) if priv != ppriv: LOGGER.warning("Privilege data error! Please share Sopel's" "raw log with the developers, if enabled. " "(Expected {} == {} for {} in {}.)".format( priv, ppriv, arg, channel)) value = mapping.get(mode[1]) if value is not None: if mode[0] == '+': priv = priv | value else: priv = priv & ~value bot.privileges[channel][arg] = priv bot.channels[channel].privileges[arg] = priv
def track_modes(bot, trigger): """Track usermode changes and keep our lists of ops up to date.""" # Mode message format: <channel> *( ( "-" / "+" ) *<modes> *<modeparams> ) channel = Identifier(trigger.args[0]) line = trigger.args[1:] # If the first character of where the mode is being set isn't a # # then it's a user mode, not a channel mode, so we'll ignore it. if channel.is_nick(): return mapping = {'v': sopel.module.VOICE, 'h': sopel.module.HALFOP, 'o': sopel.module.OP, 'a': sopel.module.ADMIN, 'q': sopel.module.OWNER} modes = [] for arg in line: if len(arg) == 0: continue if arg[0] in '+-': # There was a comment claiming IRC allows e.g. MODE +aB-c foo, but # I don't see it in any RFCs. Leaving in the extra parsing for now. sign = '' modes = [] for char in arg: if char == '+' or char == '-': sign = char else: modes.append(sign + char) else: arg = Identifier(arg) for mode in modes: priv = bot.channels[channel].privileges.get(arg, 0) # Log a warning if the two privilege-tracking data structures # get out of sync. That should never happen. # This is a good place to verify that bot.channels is doing # what it's supposed to do before ultimately removing the old, # deprecated bot.privileges structure completely. ppriv = bot.privileges[channel].get(arg, 0) if priv != ppriv: LOGGER.warning("Privilege data error! Please share Sopel's" "raw log with the developers, if enabled. " "(Expected {} == {} for {} in {}.)" .format(priv, ppriv, arg, channel)) value = mapping.get(mode[1]) if value is not None: if mode[0] == '+': priv = priv | value else: priv = priv & ~value bot.privileges[channel][arg] = priv bot.channels[channel].privileges[arg] = priv
def votemode(bot, trigger, mode): make_user_active(bot, trigger) channel = trigger.sender account = trigger.account if account is None: bot.say("You must be authed to use this command") return if bot.privileges[trigger.sender][bot.nick] < OP: return bot.reply("I'm not a channel operator!") quota = calculate_quota(bot, trigger, bot.memory['mode_threshold'][mode]) # This isn't per user but it's probably an OK heuristic if datetime.now() - bot.memory['last_vote'] > timedelta(minutes=5): clear_votes(bot) # Quota is 50% of active users plus one if trigger.group(2): target = Identifier(str(trigger.group(2)).split()[0].strip().lower()) if not target.is_nick(): return bot.reply("That is not a valid nick") if target not in bot.privileges[channel]: return bot.reply("I don't see %s." % target) target_privs = bot.privileges[channel][target] if target_privs > 0: return bot.reply("You cannot vote" + mode + " privileged users") if target in bot.memory['votes'][mode]: if str(account) not in bot.memory['votes'][mode][target]: bot.memory['votes'][mode][target].append(str(account)) else: bot.memory['votes'][mode][target] = [str(account)] bot.reply("Vote recorded. (%s more votes for action)" % str(max(0, quota - len(bot.memory['votes'][mode][target])+1))) if len(bot.memory['votes'][mode][target]) > quota: bot.memory['vote_methods'][mode](bot, channel, target) bot.memory['last_vote'] = datetime.now() elif mode == "registered" or mode == "moderated": if str(account) not in bot.memory['votes'][mode]: bot.memory['votes'][mode].append(str(account)) else: bot.memory['votes'][mode] = [str(account)] bot.reply("Vote recorded. (%s more votes for action)" % str(max(0, quota - len(bot.memory['votes'][mode])+1))) if len(bot.memory['votes'][mode]) > quota: bot.memory['vote_methods'][mode](bot, channel) bot.memory['last_vote'] = datetime.now() else: bot.say("Current active vote%s (%s needed to %s): " % (mode, str(quota + 1), mode)) for ballot in bot.memory['votes'][mode]: bot.say("%s has %s %s votes." % (ballot, len(bot.memory['votes'][mode][ballot]), mode)) return
def write_log(bot, event, channel): if bot.config.chanlogs2.allow_toggle: if not bot.db.get_channel_value(channel, 'logging'): return if not isinstance(channel, Identifier): channel = Identifier(channel) if channel.is_nick() and not bot.config.chanlogs2.privmsg: return # Don't log if we are configured not to log PMs if bot.config.chanlogs2.backend == 'postgres': write_db_line(bot, event, channel) else: write_log_line(bot, event, channel)
def track_modes(bot, trigger): """Track usermode changes and keep our lists of ops up to date.""" # Mode message format: <channel> *( ( "-" / "+" ) *<modes> *<modeparams> ) channel = Identifier(trigger.args[0]) line = trigger.args[1:] # If the first character of where the mode is being set isn't a # # then it's a user mode, not a channel mode, so we'll ignore it. if channel.is_nick(): return mapping = { 'v': sopel.module.VOICE, 'h': sopel.module.HALFOP, 'o': sopel.module.OP, 'a': sopel.module.ADMIN, 'q': sopel.module.OWNER } modes = [] for arg in line: if len(arg) == 0: continue if arg[0] in '+-': # There was a comment claiming IRC allows e.g. MODE +aB-c foo, but # I don't see it in any RFCs. Leaving in the extra parsing for now. sign = '' modes = [] for char in arg: if char == '+' or char == '-': sign = char else: modes.append(sign + char) else: arg = Identifier(arg) for mode in modes: priv = bot.privileges[channel].get(arg, 0) value = mapping.get(mode[1]) if value is not None: if mode[0] == '+': priv = priv | value else: priv = priv & ~value bot.privileges[channel][arg] = priv
def track_modes(bot, trigger): """Track usermode changes and keep our lists of ops up to date.""" # Mode message format: <channel> *( ( "-" / "+" ) *<modes> *<modeparams> ) channel = Identifier(trigger.args[0]) line = trigger.args[1:] # If the first character of where the mode is being set isn't a # # then it's a user mode, not a channel mode, so we'll ignore it. if channel.is_nick(): return mapping = { "v": sopel.module.VOICE, "h": sopel.module.HALFOP, "o": sopel.module.OP, "a": sopel.module.ADMIN, "q": sopel.module.OWNER, } modes = [] for arg in line: if len(arg) == 0: continue if arg[0] in "+-": # There was a comment claiming IRC allows e.g. MODE +aB-c foo, but # I don't see it in any RFCs. Leaving in the extra parsing for now. sign = "" modes = [] for char in arg: if char == "+" or char == "-": sign = char else: modes.append(sign + char) else: arg = Identifier(arg) for mode in modes: priv = bot.privileges[channel].get(arg, 0) value = mapping.get(mode[1]) if value is not None: if mode[0] == "+": priv = priv | value else: priv = priv & ~value bot.privileges[channel][arg] = priv
def invite(bot, trigger): """ Invite a user to the channel. """ if bot.privileges[trigger.sender][bot.nick] < HALFOP: return bot.reply("I'm not a channel operator!") text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) nick = opt channel = trigger.sender if not opt.is_nick(): nick = text[2] channel = opt invitemask = '$a:' + nick bot.write(['MODE', channel, '+I', invitemask]) bot.msg('ChanServ', 'flags {0} {1} +iV'.format(channel, nick)) bot.write(['INVITE', nick])
def kick(bot, trigger): """Kick a user from the channel.""" if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP: return bot.reply("I'm not a channel operator!") text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) nick = opt channel = trigger.sender reasonidx = 2 if not opt.is_nick(): if argc < 3: return nick = text[2] channel = opt reasonidx = 3 reason = ' '.join(text[reasonidx:]) if nick != bot.config.core.nick: bot.write(['KICK', channel, nick], reason)
def kick(bot, trigger): """Kick a user from the channel.""" if bot.channels[trigger.sender].privileges[bot.nick] < plugin.HALFOP: bot.reply(ERROR_MESSAGE_NOT_OP) return text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) nick = opt channel = trigger.sender reasonidx = 2 if not opt.is_nick(): if argc < 3: return nick = text[2] channel = opt reasonidx = 3 reason = ' '.join(text[reasonidx:]) if nick != bot.config.core.nick: bot.kick(nick, channel, reason)
def ban(bot, trigger): """Ban a user from the channel The bot must be a channel operator for this command to work. """ if bot.channels[trigger.sender].privileges[bot.nick] < HALFOP: return bot.reply("I'm not a channel operator!") text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) banmask = opt channel = trigger.sender if not opt.is_nick(): if argc < 3: return channel = opt banmask = text[2] banmask = configureHostMask(banmask) if banmask == '': return bot.write(['MODE', channel, '+b', banmask])
def unquiet(bot, trigger): """Unquiet a user The bot must be a channel operator for this command to work. """ if bot.channels[trigger.sender].privileges[bot.nick] < OP: return bot.reply("I'm not a channel operator!") text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) quietmask = opt channel = trigger.sender if not opt.is_nick(): if argc < 3: return quietmask = text[2] channel = opt quietmask = configureHostMask(quietmask) if quietmask == '': return bot.write(['MODE', channel, '-q', quietmask])
def kick(bot, trigger): """ Kick a user from the channel. """ if bot.privileges[trigger.sender][bot.nick] < HALFOP: return bot.reply("I'm not a channel operator!") text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) nick = opt channel = trigger.sender reasonidx = 2 if not opt.is_nick(): if argc < 3: return nick = text[2] channel = opt reasonidx = 3 reason = ' '.join(text[reasonidx:]) if nick != bot.config.core.nick: bot.write(['KICK', channel, nick], reason)
def unquiet(bot, trigger): """ This gives admins the ability to unquiet a user. The bot must be a Channel Operator for this command to work. """ if bot.privileges[trigger.sender][bot.nick] < OP: return bot.reply("I'm not a channel operator!") text = trigger.group().split() argc = len(text) if argc < 2: return opt = Identifier(text[1]) quietmask = opt channel = trigger.sender if not opt.is_nick(): if argc < 3: return quietmask = text[2] channel = opt quietmask = configureHostMask(quietmask) if quietmask == '': return bot.write(['MODE', channel, '-q', quietmask])
def kickban(bot, trigger): """Kick and ban a user from the channel. The bot must be a channel operator for this command to work. """ chanops = get_chanops(bot, trigger) if bot.channels[trigger.sender].privileges[ bot.nick] < OP and trigger.account in chanops: bot.say('Please wait...') bot.say('op ' + trigger.sender, 'ChanServ') time.sleep(1) text = trigger.group().split() argc = len(text) if argc < 3: bot.reply('Syntax is: .kickban <nick> <reason>') return opt = Identifier(text[1]) nick = opt mask = text[2] if any([s in text[2] for s in "!@*"]) else '' channel = trigger.sender reasonidx = 3 if mask != '' else 2 if not opt.is_nick(): if argc < 5: bot.reply('Syntax is: .kickban <nick> <reason>') return channel = opt nick = text[2] mask = text[3] if any([s in text[3] for s in "!@*"]) else '' reasonidx = 4 if mask != '' else 3 reason = ' '.join(text[reasonidx:]) mask = configureHostMask(mask) if mask == '': mask = nick + '!*@*' if trigger.account in chanops: bot.write(['MODE', channel, '+b', mask]) bot.write(['KICK', channel, nick, ':' + reason]) else: bot.reply( 'Access Denied. If in error, please contact the channel founder.')
def _parse_modes(bot, args, clear=False): """Parse MODE message and apply changes to internal state.""" channel_name = Identifier(args[0]) if channel_name.is_nick(): # We don't do anything with user modes LOGGER.debug("Ignoring user modes: %r", args) return channel = bot.channels[channel_name] # Unreal 3 sometimes sends an extraneous trailing space. If we're short an # arg, we'll find out later. if args[-1] == "": args.pop() # If any args are still empty, that's something we may not be prepared for, # but let's continue anyway hoping they're trailing / not important. if len(args) < 2 or not all(args): LOGGER.debug("The server sent a possibly malformed MODE message: %r", args) modestring = args[1] params = args[2:] mapping = { "v": plugin.VOICE, "h": plugin.HALFOP, "o": plugin.OP, "a": plugin.ADMIN, "q": plugin.OWNER, "y": plugin.OPER, "Y": plugin.OPER, } modes = {} if not clear: # Work on a copy for some thread safety modes.update(channel.modes) # Process modes sign = "" param_idx = 0 chanmodes = bot.isupport.CHANMODES for char in modestring: # Are we setting or unsetting if char in "+-": sign = char continue if char in chanmodes["A"]: # Type A (beI, etc) have a nick or address param to add/remove if char not in modes: modes[char] = set() if sign == "+": modes[char].add(params[param_idx]) elif params[param_idx] in modes[char]: modes[char].remove(params[param_idx]) param_idx += 1 elif char in chanmodes["B"]: # Type B (k, etc) always have a param if sign == "+": modes[char] = params[param_idx] elif char in modes: modes.pop(char) param_idx += 1 elif char in chanmodes["C"]: # Type C (l, etc) have a param only when setting if sign == "+": modes[char] = params[param_idx] param_idx += 1 elif char in modes: modes.pop(char) elif char in chanmodes["D"]: # Type D (aciLmMnOpqrRst, etc) have no params if sign == "+": modes[char] = True elif char in modes: modes.pop(char) elif char in mapping and ("PREFIX" not in bot.isupport or char in bot.isupport.PREFIX): # User privs modes, always have a param nick = Identifier(params[param_idx]) priv = channel.privileges.get(nick, 0) value = mapping.get(char) if value is not None: if sign == "+": priv = priv | value else: priv = priv & ~value channel.privileges[nick] = priv param_idx += 1 else: # Might be in a mode block past A/B/C/D, but we don't speak those. # Send a WHO to ensure no user priv modes we're skipping are lost. LOGGER.warning( "Unknown MODE message, sending WHO. Message was: %r", args, ) _send_who(bot, channel_name) return if param_idx != len(params): LOGGER.warning( "Too many arguments received for MODE: args=%r chanmodes=%r", args, chanmodes, ) channel.modes = modes LOGGER.info("Updated mode for channel: %s", channel.name) LOGGER.debug("Channel %r mode: %r", str(channel.name), channel.modes)
def track_modes(bot, trigger): """Track usermode changes and keep our lists of ops up to date.""" # Mode message format: <channel> *( ( "-" / "+" ) *<modes> *<modeparams> ) if len(trigger.args) < 3: # We need at least [channel, mode, nickname] to do anything useful # MODE messages with fewer args won't help us LOGGER.info("Received an apparently useless MODE message: {}" .format(trigger.raw)) return # Our old MODE parsing code checked if any of the args was empty. # Somewhere around here would be a good place to re-implement that if it's # actually necessary to guard against some non-compliant IRCd. But for now # let's just log malformed lines to the debug log. if not all(trigger.args): LOGGER.debug("The server sent a possibly malformed MODE message: {}" .format(trigger.raw)) # From here on, we will make a (possibly dangerous) assumption that the # received MODE message is more-or-less compliant channel = Identifier(trigger.args[0]) # If the first character of where the mode is being set isn't a # # then it's a user mode, not a channel mode, so we'll ignore it. # TODO: Handle CHANTYPES from ISUPPORT numeric (005) # (Actually, most of this function should be rewritten again when we parse # ISUPPORT...) if channel.is_nick(): return modestring = trigger.args[1] nicks = [Identifier(nick) for nick in trigger.args[2:]] mapping = {'v': sopel.module.VOICE, 'h': sopel.module.HALFOP, 'o': sopel.module.OP, 'a': sopel.module.ADMIN, 'q': sopel.module.OWNER} # Parse modes before doing anything else modes = [] sign = '' for char in modestring: # There was a comment claiming IRC allows e.g. MODE +aB-c foo, but it # doesn't seem to appear in any RFCs. But modern.ircdocs.horse shows # it, so we'll leave in the extra parsing for now. if char in '+-': sign = char elif char in mapping: # Filter out unexpected modes and hope they don't have parameters modes.append(sign + char) # Try to map modes to arguments, after sanity-checking if len(modes) != len(nicks) or not all([nick.is_nick() for nick in nicks]): # Something fucky happening, like unusual batching of non-privilege # modes together with the ones we expect. Way easier to just re-WHO # than try to account for non-standard parameter-taking modes. _send_who(bot, channel) return pairs = dict(zip(modes, nicks)) for (mode, nick) in pairs.items(): priv = bot.channels[channel].privileges.get(nick, 0) # Log a warning if the two privilege-tracking data structures # get out of sync. That should never happen. # This is a good place to verify that bot.channels is doing # what it's supposed to do before ultimately removing the old, # deprecated bot.privileges structure completely. ppriv = bot.privileges[channel].get(nick, 0) if priv != ppriv: LOGGER.warning("Privilege data error! Please share Sopel's" "raw log with the developers, if enabled. " "(Expected {} == {} for {} in {}.)" .format(priv, ppriv, nick, channel)) value = mapping.get(mode[1]) if value is not None: if mode[0] == '+': priv = priv | value else: priv = priv & ~value bot.privileges[channel][nick] = priv bot.channels[channel].privileges[nick] = priv
def _parse_modes(bot, args): """Parse MODE message and apply changes to internal state.""" channel_name = Identifier(args[0]) if channel_name.is_nick(): # We don't do anything with user modes return channel = bot.channels[channel_name] # Our old MODE parsing code checked for empty args. This would be a good # place to re-implement that if necessary for a non-compliant IRCd, but for # now just log malformed lines. After this we'll make a (possibly dangerous) # assumption that the MODE message is more-or-less compliant. if len(args) < 2 or not all(args): LOGGER.debug("The server sent a possibly malformed MODE message: %r", args) modestring = args[1] params = args[2:] mapping = { "v": module.VOICE, "h": module.HALFOP, "o": module.OP, "a": module.ADMIN, "q": module.OWNER, "y": module.OPER, "Y": module.OPER, } # Process modes sign = "" param_idx = 0 chanmodes = bot.isupport.CHANMODES for char in modestring: # Are we setting or unsetting if char in "+-": sign = char continue if char in chanmodes["A"]: # Type A (beI, etc) have a nick or address param to add/remove if char not in channel.modes: channel.modes[char] = set() if sign == "+": channel.modes[char].add(params[param_idx]) elif params[param_idx] in channel.modes[char]: channel.modes[char].remove(params[param_idx]) param_idx += 1 elif char in chanmodes["B"]: # Type B (k, etc) always have a param if sign == "+": channel.modes[char] = params[param_idx] elif char in channel.modes: channel.modes.pop(char) param_idx += 1 elif char in chanmodes["C"]: # Type C (l, etc) have a param only when setting if sign == "+": channel.modes[char] = params[param_idx] param_idx += 1 elif char in channel.modes: channel.modes.pop(char) elif char in chanmodes["D"]: # Type D (aciLmMnOpqrRst, etc) have no params if sign == "+": channel.modes[char] = True elif char in channel.modes: channel.modes.pop(char) elif char in mapping and ("PREFIX" not in bot.isupport or char in bot.isupport.PREFIX): # User privs modes, always have a param nick = Identifier(params[param_idx]) priv = channel.privileges.get(nick, 0) # Log a warning if the two privilege-tracking data structures # get out of sync. That should never happen. # This is a good place to verify that bot.channels is doing # what it's supposed to do before ultimately removing the old, # deprecated bot.privileges structure completely. ppriv = bot.privileges[channel_name].get(nick, 0) if priv != ppriv: LOGGER.warning( ("Privilege data error! Please share Sopel's " "raw log with the developers, if enabled. " "(Expected %s == %s for %r in %r)"), priv, ppriv, nick, channel, ) value = mapping.get(char) if value is not None: if sign == "+": priv = priv | value else: priv = priv & ~value bot.privileges[channel_name][nick] = priv channel.privileges[nick] = priv param_idx += 1 else: # Might be in a mode block past A/B/C/D, but we don't speak those. # Send a WHO to ensure no user priv modes we're skipping are lost. LOGGER.warning( "Unknown MODE message, sending WHO. Message was: %r", args, ) _send_who(bot, channel_name) return