Esempio n. 1
0
def bomb_glue(bot, trigger):
    old = trigger.nick
    new = Identifier(trigger)
    with lock:
        if old.lower() in BOMBS:
            BOMBS[new.lower()] = BOMBS.pop(old.lower())
            bot.notice(STRINGS['BOMB_STILL'] % new, new)
Esempio n. 2
0
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)
Esempio n. 3
0
 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)
Esempio n. 4
0
def track_nicks(bot, trigger):
    """Track nickname changes and maintain our chanops list accordingly."""
    old = trigger.nick
    new = Identifier(trigger)

    # Give debug mssage, and PM the owner, if the bot's own nick changes.
    if old == bot.nick and new != bot.nick:
        privmsg = ("Hi, I'm your bot, %s."
                   "Something has made my nick change. "
                   "This can cause some problems for me, "
                   "and make me do weird things. "
                   "You'll probably want to restart me, "
                   "and figure out what made that happen "
                   "so you can stop it happening again. "
                   "(Usually, it means you tried to give me a nick "
                   "that's protected by NickServ.)") % bot.nick
        debug_msg = ("Nick changed by server. "
            "This can cause unexpected behavior. Please restart the bot.")
        LOGGER.critical(debug_msg)
        bot.msg(bot.config.core.owner, privmsg)
        return

    for channel in bot.privileges:
        channel = Identifier(channel)
        if old in bot.privileges[channel]:
            value = bot.privileges[channel].pop(old)
            bot.privileges[channel][new] = value

    for channel in bot.channels.values():
        channel.rename_user(old, new)
    if old in bot.users:
        bot.users[new] = bot.users.pop(old)
Esempio n. 5
0
def cutwire(bot, trigger):
    """
    Tells sopel to cut a wire when you've been bombed.
    """
    global bombs, colors
    target = Identifier(trigger.nick)
    if target.lower() != bot.nick.lower() and target.lower() not in bombs:
        bot.say('You can\'t cut a wire till someone bombs you')
        return
    if not trigger.group(2):
        bot.say('You have to choose a wire to cut.')
        return
    color, code = bombs.pop(target.lower())  # remove target from bomb list
    wirecut = trigger.group(2).rstrip(' ')
    if wirecut.lower() in ('all', 'all!'):
        sch.cancel(code)  # defuse timer, execute premature detonation
        kmsg = ('KICK %s %s : Cutting ALL the wires! *boom* (You should\'ve picked the %s wire.)'
                % (trigger.sender, target, color))
        bot.write([kmsg])
    elif wirecut.capitalize() not in colors:
        bot.say('I can\'t seem to find that wire, ' + target + '! You sure you\'re picking the right one? It\'s not here!')
        bombs[target.lower()] = (color, code)  # Add the target back onto the bomb list,
    elif wirecut.capitalize() == color:
        bot.say('You did it, ' + target + '! I\'ll be honest, I thought you were dead. But nope, you did it. You picked the right one. Well done.')
        sch.cancel(code)  # defuse bomb
    else:
        sch.cancel(code)  # defuse timer, execute premature detonation
        kmsg = 'KICK ' + trigger.sender + ' ' + target + \
               ' : No! No, that\'s the wrong one. Aww, you\'ve gone and killed yourself. Oh, that\'s... that\'s not good. No good at all, really. Wow. Sorry. (You should\'ve picked the ' + color + ' wire.)'
        bot.write([kmsg])
Esempio n. 6
0
def start(bot, trigger):
    """
    Put a bomb in the specified user's pants. They will be kicked if they
     don't guess the right wire fast enough.
    """
    if not trigger.group(3):
        bot.say('Who do you want to Bomb?')
        return
    if not trigger.sender.startswith('#'):
        bot.say('Tell me this in a channel')
        return
    global bombs
    global sch
    target = Identifier(trigger.group(3))
    if target == bot.nick:
        bot.say('I will NOT BOMB MYSELF!')
        return
    if target.lower() in bombs:
        bot.say('I can\'t fit another bomb in ' + target + '\'s pants!')
        return
    if target == trigger.nick:
        bot.say('I will not LET YOU BOMB YOURSELF!')
        return
    if target.lower() not in bot.privileges[trigger.sender.lower()]:
        bot.say('Please Bomb someone WHO IS HERE!')
        return
    message = 'Hey, ' + target + '! Don\'t look but, I think there\'s a bomb in your pants. 2 minute timer, 5 wires: Red, Yellow, Blue, White and Black. Which wire should I cut? Don\'t worry, I know what I\'m doing! (respond with .cutwire color)'
    bot.say(message)
    color = choice(colors)
    bot.msg(trigger.nick,
               "Hey, don\'t tell %s, but the %s wire? Yeah, that\'s the one."
               " But shh! Don\'t say anything!" % (target, color))
    code = sch.enter(fuse, 1, explode, (bot, trigger))
    bombs[target.lower()] = (color, code)
    sch.run()
Esempio n. 7
0
def cancel_bomb(bot, trigger):
    """
    Lets a bomber disarm the bomb they set on the specified user. Does not reset the cooldown timer.
    (Bot admins can cancel bombs on any player in the channel.)
    """
    target = trigger.group(3) or None
    if not target:
        for bomb in BOMBS:
            if trigger.nick == BOMBS[bomb]['bomber']:
                target = BOMBS[bomb]['target']
                break
        if not target:
            return bot.reply(STRINGS['CANCEL_WHOM'])
    target = Identifier(target)  # issue #24
    with lock:
        if target.lower() not in BOMBS:
            bot.reply(STRINGS['CANCEL_NO_BOMB'] % target)
            return
        if trigger.nick != BOMBS[target.lower()]['bomber'] and not trigger.admin:
            bot.reply(STRINGS['CANCEL_NO_PERMISSION'] % target)
            return
        bomber = BOMBS[target.lower()]['bomber']
        bombs_planted = bot.db.get_nick_value(bomber, 'bombs_planted') or 0
        bot.db.set_nick_value(bomber, 'bombs_planted', bombs_planted - 1)
        BOMBS.pop(target.lower())['timer'].cancel()
        bot.say(STRINGS['CANCEL_DONE'] % target)
Esempio n. 8
0
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
Esempio n. 9
0
 def get_nick_value(self, nick, key):
     """Retrieves the value for a given key associated with a nick."""
     nick = Identifier(nick)
     result = self.execute(
         'SELECT value FROM nicknames JOIN nick_values '
         'ON nicknames.nick_id = nick_values.nick_id '
         'WHERE slug = ? AND key = ?',
         [nick.lower(), key]
     ).fetchone()
     if result is not None:
         result = result[0]
     return _deserialize(result)
Esempio n. 10
0
    def unalias_nick(self, alias):
        """Removes an alias.

        Raises ValueError if there is not at least one other nick in the group.
        To delete an entire group, use `delete_group`.
        """
        alias = Identifier(alias)
        nick_id = self.get_nick_id(alias, False)
        count = self.execute('SELECT COUNT(*) FROM nicknames WHERE nick_id = ?',
                             [nick_id]).fetchone()[0]
        if count == 0:
            raise ValueError('Given alias is the only entry in its group.')
        self.execute('DELETE FROM nicknames WHERE slug = ?', [alias.lower()])
Esempio n. 11
0
def explode(bot, trigger):
    target = Identifier(trigger.group(3))
    orig_target = target
    with lock:
        if target.lower() not in BOMBS:  # nick change happened
            for nick in BOMBS.keys():
                if BOMBS[nick]['target'] == target:
                    target = Identifier(nick)
                    break
        bot.say(STRINGS['NEVER_TRIED'] % (target, BOMBS[target.lower()]['color']))
        kickboom(bot, trigger, target)
        BOMBS.pop(target.lower())
    timeouts = bot.db.get_nick_value(orig_target, 'bomb_timeouts') or 0
    bot.db.set_nick_value(orig_target, 'bomb_timeouts', timeouts + 1)
Esempio n. 12
0
    def alias_nick(self, nick, alias):
        """Create an alias for a nick.

        Raises ValueError if the alias already exists. If nick does not already
        exist, it will be added along with the alias."""
        nick = Identifier(nick)
        alias = Identifier(alias)
        nick_id = self.get_nick_id(nick)
        sql = 'INSERT INTO nicknames (nick_id, slug, canonical) VALUES (?, ?, ?)'
        values = [nick_id, alias.lower(), alias]
        try:
            self.execute(sql, values)
        except sqlite3.IntegrityError as e:
            raise ValueError('Alias already exists. {0}'.format(e))
Esempio n. 13
0
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
Esempio n. 14
0
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)
Esempio n. 15
0
 def get_nick_value(self, nick, key):
     """Retrieves the value for a given key associated with a nick."""
     nick = Identifier(nick)
     session = self.ssession()
     try:
         result = session.query(NickValues) \
             .filter(Nicknames.nick_id == NickValues.nick_id) \
             .filter(Nicknames.slug == nick.lower()) \
             .filter(NickValues.key == key) \
             .one_or_none()
         if result is not None:
             result = result.value
         return _deserialize(result)
     except SQLAlchemyError:
         session.rollback()
         raise
     finally:
         session.close()
Esempio n. 16
0
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
Esempio n. 17
0
def cancel_bomb(bot, trigger):
    """
    Cancel the bomb placed on the specified player (can also be used by admins).
    """
    target = trigger.group(3) or None
    if not target:
        bot.reply(STRINGS['CANCEL_WHOM'])
        return
    target = Identifier(target)  # issue #24
    with lock:
        if target.lower() not in BOMBS:
            bot.reply(STRINGS['CANCEL_NO_BOMB'] % target)
            return
        if trigger.nick != BOMBS[target.lower()]['bomber'] and not trigger.admin:
            bot.reply(STRINGS['CANCEL_NO_PERMISSION'] % target)
            return
        bomber = BOMBS[target.lower()]['bomber']
        bombs_planted = bot.db.get_nick_value(bomber, 'bombs_planted') or 0
        bot.db.set_nick_value(bomber, 'bombs_planted', bombs_planted - 1)
        BOMBS.pop(target.lower())['timer'].cancel()
        bot.say(STRINGS['CANCEL_DONE'] % target)
Esempio n. 18
0
def test_bot_mixed_mode_types(mockbot, ircfactory):
    """Ensure mixed argument-required and -not-required modes are handled.

    Sopel 6.6.6 and older did not behave well.

    .. seealso::

        GitHub issue #1575 (https://github.com/sopel-irc/sopel/pull/1575).
    """
    irc = ircfactory(mockbot)
    irc.bot._isupport = isupport.ISupport(chanmodes=("be", "", "", "mn",
                                                     tuple()))
    irc.bot.modeparser.chanmodes = irc.bot.isupport.CHANMODES
    irc.channel_joined(
        '#test', ['Uvoice', 'Uop', 'Uadmin', 'Uvoice2', 'Uop2', 'Uadmin2'])
    irc.mode_set('#test', '+amovn', ['Uadmin', 'Uop', 'Uvoice'])

    assert mockbot.channels["#test"].privileges[Identifier("Uadmin")] == ADMIN
    assert mockbot.channels["#test"].modes["m"]
    assert mockbot.channels["#test"].privileges[Identifier("Uop")] == OP
    assert mockbot.channels["#test"].privileges[Identifier("Uvoice")] == VOICE
    assert mockbot.channels["#test"].modes["n"]

    irc.mode_set('#test', '+above',
                 ['Uadmin2', 'x!y@z', 'Uop2', 'Uvoice2', 'a!b@c'])

    assert mockbot.channels["#test"].privileges[Identifier("Uadmin2")] == ADMIN
    assert "x!y@z" in mockbot.channels["#test"].modes["b"]
    assert mockbot.channels["#test"].privileges[Identifier("Uop2")] == OP
    assert mockbot.channels["#test"].privileges[Identifier("Uvoice2")] == VOICE
    assert "a!b@c" in mockbot.channels["#test"].modes["e"]
Esempio n. 19
0
def test_bot_mixed_mode_types(mockbot, ircfactory):
    """Ensure mixed argument-required and -not-required modes are handled.

    Sopel 6.6.6 and older did not behave well.

    .. seealso::

        GitHub issue #1575 (https://github.com/sopel-irc/sopel/pull/1575).
    """
    irc = ircfactory(mockbot)
    irc.channel_joined(
        '#test', ['Uvoice', 'Uop', 'Uadmin', 'Uvoice2', 'Uop2', 'Uadmin2'])
    irc.mode_set('#test', '+amov', ['Uadmin', 'Uop', 'Uvoice'])

    assert mockbot.channels["#test"].privileges[Identifier("Uadmin")] == ADMIN
    assert mockbot.channels["#test"].privileges[Identifier("Uop")] == OP
    assert mockbot.channels["#test"].privileges[Identifier("Uvoice")] == VOICE

    irc.mode_set('#test', '+abov', ['Uadmin2', 'x!y@z', 'Uop2', 'Uvoice2'])

    assert mockbot.channels["#test"].privileges[Identifier("Uadmin2")] == 0
    assert mockbot.channels["#test"].privileges[Identifier("Uop2")] == 0
    assert mockbot.channels["#test"].privileges[Identifier("Uvoice2")] == 0

    assert mockbot.backend.message_sent == rawlist('WHO #test'), (
        'Upon finding an unexpected nick, the bot must send a WHO request.')
Esempio n. 20
0
def test_call_rule_rate_limited_user(mockbot):
    items = []

    # setup
    def testrule(bot, trigger):
        bot.say('hi')
        items.append(1)
        return "Return Value"

    rule_hello = rules.Rule(
        [re.compile(r'(hi|hello|hey|sup)')],
        plugin='testplugin',
        label='testrule',
        handler=testrule,
        rate_limit=100,
        threaded=False,
    )

    # trigger
    line = ':[email protected] PRIVMSG #channel :hello'
    pretrigger = trigger.PreTrigger(mockbot.nick, line)

    # match
    matches = list(rule_hello.match(mockbot, pretrigger))
    match = matches[0]

    # trigger and wrapper
    rule_trigger = trigger.Trigger(mockbot.settings,
                                   pretrigger,
                                   match,
                                   account=None)
    wrapper = bot.SopelWrapper(mockbot, rule_trigger)

    # call rule
    mockbot.call_rule(rule_hello, wrapper, rule_trigger)

    # assert the rule has been executed
    assert mockbot.backend.message_sent == rawlist('PRIVMSG #channel :hi')
    assert items == [1]

    # assert the rule is now rate limited
    assert rule_hello.is_rate_limited(Identifier('Test'))
    assert not rule_hello.is_channel_rate_limited('#channel')
    assert not rule_hello.is_global_rate_limited()

    # call rule again
    mockbot.call_rule(rule_hello, wrapper, rule_trigger)

    # assert no new message
    assert mockbot.backend.message_sent == rawlist(
        'PRIVMSG #channel :hi'), 'There must not be any new message sent'
    assert items == [1], 'There must not be any new item'
Esempio n. 21
0
def track_kick(bot, trigger):
    nick = Identifier(trigger.args[1])
    if nick == bot.nick:
        bot.channels.remove(trigger.sender)
        del bot.privileges[trigger.sender]
    else:
        # Temporary fix to stop KeyErrors from being sent to channel
        # The privileges dict may not have all nicks stored at all times
        # causing KeyErrors
        try:
            del bot.privileges[trigger.sender][nick]
        except KeyError:
            pass
Esempio n. 22
0
 def adjust_channel_value(self, channel, key, value):
     """Adjusts the value for a given key to be associated with the channel."""
     channel = Identifier(channel).lower()
     result = self.execute(
         'SELECT value FROM channel_values WHERE channel = ? AND key = ?',
         [channel, key]).fetchone()
     if result is not None:
         result = result[0]
     current_value = _deserialize(result)
     value = current_value + value
     value = json.dumps(value, ensure_ascii=False)
     self.execute('INSERT OR REPLACE INTO channel_values VALUES (?, ?, ?)',
                  [channel, key, value])
Esempio n. 23
0
def test_basic_pretrigger(nick):
    line = ':[email protected] PRIVMSG #Sopel :Hello, world'
    pretrigger = PreTrigger(nick, line)
    assert pretrigger.tags == {}
    assert pretrigger.hostmask == '[email protected]'
    assert pretrigger.line == line
    assert pretrigger.args == ['#Sopel', 'Hello, world']
    assert pretrigger.text == 'Hello, world'
    assert pretrigger.event == 'PRIVMSG'
    assert pretrigger.nick == Identifier('Foo')
    assert pretrigger.user == 'foo'
    assert pretrigger.host == 'example.com'
    assert pretrigger.sender == '#Sopel'
Esempio n. 24
0
def test_quit_pretrigger(nick):
    line = ':[email protected] QUIT :quit message text'
    pretrigger = PreTrigger(nick, line)
    assert pretrigger.tags == {}
    assert pretrigger.hostmask == '[email protected]'
    assert pretrigger.line == line
    assert pretrigger.args == ['quit message text']
    assert pretrigger.text == 'quit message text'
    assert pretrigger.event == 'QUIT'
    assert pretrigger.nick == Identifier('Foo')
    assert pretrigger.user == 'foo'
    assert pretrigger.host == 'example.com'
    assert pretrigger.sender is None
Esempio n. 25
0
def unexclude(bot, trigger):
    """
    Re-enable bombing yourself (admins: or another user)
    """
    if not trigger.group(3):
        target = trigger.nick
    else:
        target = Identifier(trigger.group(3))
    if not trigger.admin and target != trigger.nick:
        bot.say(STRINGS['ADMINS_MARK_BOMBABLE'])
        return
    bot.db.set_nick_value(target, 'unbombable', False)
    bot.say(STRINGS['MARKED_BOMBABLE'] % target)
Esempio n. 26
0
def test_bot_unknown_mode(mockbot, ircfactory):
    """Ensure modes not in PREFIX or CHANMODES trigger a WHO."""
    irc = ircfactory(mockbot)
    irc.bot._isupport = isupport.ISupport(chanmodes=("b", "", "", "mnt",
                                                     tuple()))
    irc.bot.modeparser.chanmodes = irc.bot.isupport.CHANMODES
    irc.channel_joined("#test", ["Alex", "Bob", "Cheryl"])
    irc.mode_set("#test", "+te", ["Alex"])

    assert mockbot.channels["#test"].privileges[Identifier("Alex")] == 0
    assert mockbot.backend.message_sent == rawlist(
        "WHO #test"
    ), "Upon finding an unknown mode, the bot must send a WHO request."
Esempio n. 27
0
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)
Esempio n. 28
0
    def unalias_nick(self, alias):
        """Removes an alias.

        Raises ValueError if there is not at least one other nick in the group.
        To delete an entire group, use `delete_group`.
        """
        alias = Identifier(alias)
        nick_id = self.get_nick_id(alias, False)
        session = self.ssession()
        try:
            count = session.query(Nicknames) \
                .filter(Nicknames.nick_id == nick_id) \
                .count()
            if count <= 1:
                raise ValueError('Given alias is the only entry in its group.')
            session.query(Nicknames).filter(Nicknames.slug == alias.lower()).delete()
            session.commit()
        except SQLAlchemyError:
            session.rollback()
            raise
        finally:
            session.close()
Esempio n. 29
0
def fighterStatus(bot, trigger):

	if not trigger.group(2):
		bot.say('fighter status for who?')
		return
	targetNick = Identifier(trigger.group(2).strip())
	hitpoints = bot.db.get_nick_value(targetNick,'hitPoints')
	xl = bot.db.get_nick_value(targetNick,'xl')
	if not hitpoints:
		bot.say('I can''t find stats for {nick}'.format(nick=targetNick))
		return
	else:
		bot.say('{nick} has {hp} hit points / {max} @ Level {xl} with {xp} xp until the next level'.format(nick=targetNick, hp=hitpoints,max=100 + (xl*3), xl=xl,xp=xlMap[bot.db.get_nick_value(targetNick,'xl') + 1] - bot.db.get_nick_value(targetNick,'xp')))
Esempio n. 30
0
    def rpl_names(self, bot, trigger):
        """Handle NAMES response, happens when joining to channels."""
        names = trigger.split()

        # TODO specific to one channel type. See issue 281.
        channels = re.search(r'(#\S*)', trigger.raw)
        if not channels:
            return
        channel = Identifier(channels.group(1))
        self.add_channel(channel)

        mapping = {
            '+': sopel.module.VOICE,
            '%': sopel.module.HALFOP,
            '@': sopel.module.OP,
            '&': sopel.module.ADMIN,
            '~': sopel.module.OWNER
        }

        for name in names:
            nick = Identifier(name.lstrip(''.join(mapping.keys())))
            self.add_to_channel(channel, nick)
Esempio n. 31
0
def cutwire(bot, trigger):
    """
    Tells sopel to cut a wire when you've been bombed.
    """
    global BOMBS
    target = Identifier(trigger.nick)
    if target == bot.nick:  # a parallel bot behind a bouncer (e.g. Bucket) can trigger this function (see #16)
        return
    with lock:
        if target.lower() != bot.nick.lower() and target.lower() not in BOMBS:
            bot.say(STRINGS['CUT_NO_BOMB'] % target)
            return
        if not trigger.group(3):
            bot.say(STRINGS['CUT_NO_WIRE'])
            return
        # Remove target from bomb list temporarily
        bomb = BOMBS.pop(target.lower())
        wirecut = trigger.group(3)
        if wirecut.lower() in ('all', 'all!'):
            bomb['timer'].cancel()  # defuse timer, execute premature detonation
            bot.say(STRINGS['CUT_ALL_WIRES'] % bomb['color'])
            kickboom(bot, trigger, target)
            alls = bot.db.get_nick_value(bomb['target'], 'bomb_alls') or 0
            bot.db.set_nick_value(bomb['target'], 'bomb_alls', alls + 1)
        elif wirecut.capitalize() not in bomb['wires']:
            bot.say(STRINGS['CUT_IMAGINARY'] % target)
            # Add the target back onto the bomb list
            BOMBS[target.lower()] = bomb
        elif wirecut.capitalize() == bomb['color']:
            bot.say(STRINGS['CUT_CORRECT'] % target)
            bomb['timer'].cancel()  # defuse bomb
            defuses = bot.db.get_nick_value(bomb['target'], 'bomb_defuses') or 0
            bot.db.set_nick_value(bomb['target'], 'bomb_defuses', defuses + 1)
        else:
            bomb['timer'].cancel()  # defuse timer, execute premature detonation
            bot.say(STRINGS['CUT_WRONG'] % bomb['color'])
            kickboom(bot, trigger, target)
            wrongs = bot.db.get_nick_value(bomb['target'], 'bomb_wrongs') or 0
            bot.db.set_nick_value(bomb['target'], 'bomb_wrongs', wrongs + 1)
Esempio n. 32
0
File: db.py Progetto: kwaaak/sopel
    def unalias_nick(self, alias):
        """Removes an alias.

        Raises ValueError if there is not at least one other nick in the group.
        To delete an entire group, use `delete_group`.
        """
        alias = Identifier(alias)
        nick_id = self.get_nick_id(alias, False)
        session = self.ssession()
        try:
            count = session.query(Nicknames) \
                .filter(Nicknames.nick_id == nick_id) \
                .count()
            if count <= 1:
                raise ValueError('Given alias is the only entry in its group.')
            session.query(Nicknames).filter(Nicknames.slug == alias.lower()).delete()
            session.commit()
        except SQLAlchemyError:
            session.rollback()
            raise
        finally:
            self.ssession.remove()
Esempio n. 33
0
    def merge_nick_groups(self, first_nick, second_nick):
        """Merges the nick groups for the specified nicks.

        Takes two nicks, which may or may not be registered.  Unregistered
        nicks will be registered. Keys which are set for only one of the given
        nicks will be preserved. Where multiple nicks have values for a given
        key, the value set for the first nick will be used.

        Note that merging of data only applies to the native key-value store.
        If modules define their own tables which rely on the nick table, they
        will need to have their merging done separately."""
        first_id = self.get_nick_id(Identifier(first_nick))
        second_id = self.get_nick_id(Identifier(second_nick))
        session = self.ssession()
        try:
            # Get second_id's values
            res = session.query(NickValues).filter(
                NickValues.nick_id == second_id).all()
            # Update first_id with second_id values if first_id doesn't have that key
            for row in res:
                first_res = session.query(NickValues) \
                    .filter(NickValues.nick_id == first_id) \
                    .filter(NickValues.key == row.key) \
                    .one_or_none()
                if not first_res:
                    self.set_nick_value(first_nick, row.key,
                                        _deserialize(row.value))
            session.query(NickValues).filter(
                NickValues.nick_id == second_id).delete()
            session.query(Nicknames) \
                .filter(Nicknames.nick_id == second_id) \
                .update({'nick_id': first_id})
            session.commit()
        except SQLAlchemyError:
            session.rollback()
            raise
        finally:
            session.close()
Esempio n. 34
0
 def kick(self, bot, trigger):
     targetnick = Identifier(str(trigger.args[1]))
     # bot block
     if targetnick == bot.nick:
         self.remove_all_from_channel(trigger.sender)
         self.lock.acquire()
         self.chandict[trigger.sender]["joined"] = False
         self.chandict[trigger.sender]["reason"] = "kicked"
         self.lock.release()
         return
     # Identify
     nick_id = self.whois_ident(targetnick)
     # Verify nick is not in the channel list
     self.remove_from_channel(trigger.sender, targetnick, nick_id)
Esempio n. 35
0
File: db.py Progetto: kwaaak/sopel
 def delete_nick_group(self, nick):
     """Removes a nickname, and all associated aliases and settings."""
     nick = Identifier(nick)
     nick_id = self.get_nick_id(nick, False)
     session = self.ssession()
     try:
         session.query(Nicknames).filter(Nicknames.nick_id == nick_id).delete()
         session.query(NickValues).filter(NickValues.nick_id == nick_id).delete()
         session.commit()
     except SQLAlchemyError:
         session.rollback()
         raise
     finally:
         self.ssession.remove()
Esempio n. 36
0
def _record_who(bot, channel, user, host, nick, account=None, away=None, modes=None):
    nick = Identifier(nick)
    channel = Identifier(channel)
    if nick not in bot.users:
        bot.users[nick] = User(nick, user, host)
    user = bot.users[nick]
    if account == '0':
        user.account = None
    else:
        user.account = account
    user.away = away
    priv = 0
    if modes:
        mapping = {'+': sopel.module.VOICE,
           '%': sopel.module.HALFOP,
           '@': sopel.module.OP,
           '&': sopel.module.ADMIN,
           '~': sopel.module.OWNER}
        for c in modes:
            priv = priv | mapping[c]
    if channel not in bot.channels:
        bot.channels[channel] = Channel(channel)
    bot.channels[channel].add_user(user, privs=priv)
Esempio n. 37
0
    def _nick_blocked(self, nick):
        """Check if a nickname is blocked.

        :param str nick: the nickname to check
        """
        bad_nicks = self.config.core.nick_blocks
        for bad_nick in bad_nicks:
            bad_nick = bad_nick.strip()
            if not bad_nick:
                continue
            if (re.match(bad_nick + '$', nick, re.IGNORECASE)
                    or Identifier(bad_nick) == nick):
                return True
        return False
Esempio n. 38
0
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])
Esempio n. 39
0
def test_tags_pretrigger(nick):
    line = '@foo=bar;baz;sopel.chat/special=value :[email protected] PRIVMSG #Sopel :Hello, world'
    pretrigger = PreTrigger(nick, line)
    assert pretrigger.tags == {'baz': None,
                               'foo': 'bar',
                               'sopel.chat/special': 'value'}
    assert pretrigger.hostmask == '[email protected]'
    assert pretrigger.line == line
    assert pretrigger.args == ['#Sopel', 'Hello, world']
    assert pretrigger.event == 'PRIVMSG'
    assert pretrigger.nick == Identifier('Foo')
    assert pretrigger.user == 'foo'
    assert pretrigger.host == 'example.com'
    assert pretrigger.sender == '#Sopel'
Esempio n. 40
0
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])
Esempio n. 41
0
def test_bot_mixed_mode_removal(mockbot, ircfactory):
    """Ensure mixed mode types like ``-h+a`` are handled.

    Sopel 6.6.6 and older did not handle this correctly.

    .. seealso::

        GitHub issue #1575 (https://github.com/sopel-irc/sopel/pull/1575).
    """
    irc = ircfactory(mockbot)
    irc.channel_joined('#test', ['Uvoice', 'Uop'])

    irc.mode_set('#test', '+qao', ['Uvoice', 'Uvoice', 'Uvoice'])
    assert mockbot.channels["#test"].privileges[Identifier("Uop")] == 0
    assert mockbot.channels["#test"].privileges[Identifier("Uvoice")] == (
        ADMIN + OWNER + OP), 'Uvoice got +q, +a, and +o modes'

    irc.mode_set('#test', '-o+o-qa+v',
                 ['Uvoice', 'Uop', 'Uvoice', 'Uvoice', 'Uvoice'])
    assert mockbot.channels["#test"].privileges[Identifier("Uop")] == OP, (
        'OP got +o only')
    assert mockbot.channels["#test"].privileges[Identifier(
        "Uvoice")] == VOICE, ('Uvoice got -o, -q, -a, then +v')
Esempio n. 42
0
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])
Esempio n. 43
0
def _send_who(bot, channel):
    if 'WHOX' in bot.isupport:
        # WHOX syntax, see http://faerion.sourceforge.net/doc/irc/whox.var
        # Needed for accounts in WHO replies. The `CORE_QUERYTYPE` parameter
        # for WHO is used to identify the reply from the server and confirm
        # that it has the requested format. WHO replies with different
        # querytypes in the response were initiated elsewhere and will be
        # ignored.
        bot.write(['WHO', channel, 'a%nuachtf,' + CORE_QUERYTYPE])
    else:
        # We might be on an old network, but we still care about keeping our
        # user list updated
        bot.write(['WHO', channel])
    bot.channels[Identifier(channel)].last_who = datetime.datetime.utcnow()
Esempio n. 44
0
 def rpl_who(self, bot, trigger):
     if len(trigger.args) < 2 or trigger.args[1] not in self.who_reqs:
         # Ignored, some module probably called WHO
         return
     if len(trigger.args) != 8:
         return
     _, _, channel, user, host, nick, status, account = trigger.args
     nick = Identifier(nick)
     channel = Identifier(channel)
     # Identify
     nick_id = self.whois_ident(nick)
     # Verify nick is in the all list
     self.add_to_all(nick, nick_id)
     # Verify nick is in the all list
     self.add_to_current(nick, nick_id)
     # set current nick
     self.mark_current_nick(nick, nick_id)
     # add joined channel to nick list
     self.add_channel(channel, nick_id)
     # mark user as online
     self.mark_user_online(nick_id)
     # check if nick is registered
     self.whois_send(bot, nick)
Esempio n. 45
0
    def check_user_import(self, nick, nick_id=None):
        if not nick_id:
            nick = Identifier(nick)
            nick_id = botusers.get_nick_id(nick, True)
        if nick_id not in list(self.dict["sessioncache"].keys()):
            self.dict["sessioncache"][nick_id] = botdb.get_nick_value(nick, 'botai') or {}
            for predicate in list(self.dict["sessioncache"][nick_id].keys()):
                predval = self.dict["sessioncache"][nick_id][predicate]
                self.aiml_kernel.setPredicate(predicate, predval, nick_id)

        # defaults
        if "nick" not in list(self.dict["sessioncache"][nick_id].keys()):
            self.dict["sessioncache"][nick_id]["nick"] = nick
            self.aiml_kernel.setPredicate("nick", nick, nick_id)
Esempio n. 46
0
    def on_message(self, bot, trigger, message):
        nick = Identifier(trigger.nick)
        nick_id = botusers.get_nick_id(nick, True)
        self.check_user_import(nick, nick_id)

        message = self.bot_message_precipher(bot, trigger, message)
        aiml_response = self.aiml_kernel.respond(message, nick_id)
        if aiml_response:
            aiml_response = self.bot_message_decipher(bot, trigger, aiml_response)

        self.save_nick_session(nick, nick_id)
        self.save_brain()

        return aiml_response
Esempio n. 47
0
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)
Esempio n. 48
0
def test_ctcp_action_pretrigger(nick):
    line = ':[email protected] PRIVMSG #Sopel :\x01ACTION Hello, world\x01'
    pretrigger = PreTrigger(nick, line)
    assert pretrigger.tags == {'intent': 'ACTION'}
    assert pretrigger.hostmask == '[email protected]'
    assert pretrigger.line == line
    assert pretrigger.args == ['#Sopel', 'Hello, world']
    assert pretrigger.text == '\x01ACTION Hello, world\x01'
    assert pretrigger.plain == 'Hello, world'
    assert pretrigger.event == 'PRIVMSG'
    assert pretrigger.nick == Identifier('Foo')
    assert pretrigger.user == 'foo'
    assert pretrigger.host == 'example.com'
    assert pretrigger.sender == '#Sopel'
Esempio n. 49
0
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])
Esempio n. 50
0
    def get_channel_slug(self, chan):
        """Return the case-normalized representation of ``channel``.

        :param str channel: the channel name to normalize, with prefix
                            (required)
        :return str: the case-normalized channel name (or "slug"
                     representation)

        This is useful to make sure that a channel name is stored consistently
        in both the bot's own database and third-party plugins'
        databases/files, without regard for variation in case between
        different clients and/or servers on the network.
        """
        chan = Identifier(chan)
        slug = chan.lower()
        session = self.ssession()
        try:
            count = session.query(ChannelValues) \
                .filter(ChannelValues.channel == slug) \
                .count()

            if count == 0:
                # see if it needs case-mapping migration
                old_rows = session.query(ChannelValues) \
                    .filter(ChannelValues.channel == Identifier._lower_swapped(chan))
                old_count = old_rows.count()
                if old_count > 0:
                    # it does!
                    old_rows.update({ChannelValues.channel: slug})
                    session.commit()

            return slug
        except SQLAlchemyError:
            session.rollback()
            raise
        finally:
            self.ssession.remove()
Esempio n. 51
0
def _record_who(bot, channel, user, host, nick, account=None, away=None, modes=None):
    nick = Identifier(nick)
    channel = Identifier(channel)
    if nick not in bot.users:
        usr = target.User(nick, user, host)
        bot.users[nick] = usr
    else:
        usr = bot.users[nick]
        # check for & fill in sparse User added by handle_names()
        if usr.host is None and host:
            usr.host = host
        if usr.user is None and user:
            usr.user = user
    if account == '0':
        usr.account = None
    else:
        usr.account = account
    if away is not None:
        usr.away = away
    priv = 0
    if modes:
        mapping = {
            "+": module.VOICE,
            "%": module.HALFOP,
            "@": module.OP,
            "&": module.ADMIN,
            "~": module.OWNER,
            "!": module.OPER,
        }
        for c in modes:
            priv = priv | mapping[c]
    if channel not in bot.channels:
        bot.channels[channel] = target.Channel(channel)
    bot.channels[channel].add_user(usr, privs=priv)
    if channel not in bot.privileges:
        bot.privileges[channel] = dict()
    bot.privileges[channel][nick] = priv
Esempio n. 52
0
    def alias_nick(self, nick, alias):
        """Create an alias for a nick.

        Raises ValueError if the alias already exists. If nick does not already
        exist, it will be added along with the alias."""
        nick = Identifier(nick)
        alias = Identifier(alias)
        nick_id = self.get_nick_id(nick)
        session = self.ssession()
        try:
            result = session.query(Nicknames) \
                .filter(Nicknames.slug == alias.lower()) \
                .filter(Nicknames.canonical == alias) \
                .one_or_none()
            if result:
                raise ValueError('Given alias is the only entry in its group.')
            nickname = Nicknames(nick_id=nick_id, slug=alias.lower(), canonical=alias)
            session.add(nickname)
            session.commit()
        except SQLAlchemyError:
            session.rollback()
            raise
        finally:
            session.close()
Esempio n. 53
0
File: rep.py Progetto: dgw/sopel-rep
def luv_h8(bot, trigger, target, which, warn_nonexistent=True):
    target = Identifier(target)
    which = which.lower()  # issue #18
    pfx = change = selfreply = None  # keep PyCharm & other linters happy
    if target.lower() not in bot.privileges[trigger.sender.lower()]:
        if warn_nonexistent:
            bot.reply("You can only %s someone who is here." % which)
        return
    if rep_too_soon(bot, trigger.nick):
        return
    if which == 'luv':
        selfreply = "No narcissism allowed!"
        pfx, change = 'in', 1
    if which == 'h8':
        selfreply = "Go to 4chan if you really hate yourself!"
        pfx, change = 'de', -1
    if not (pfx and change and selfreply):  # safeguard against leaving something in the above mass-None assignment
        bot.say("Logic error! Please report this to %s." % bot.config.core.owner)
        return
    if is_self(bot, trigger.nick, target):
        bot.reply(selfreply)
        return
    rep = mod_rep(bot, trigger.nick, target, change)
    bot.say("%s has %screased %s's reputation score to %d" % (trigger.nick, pfx, target, rep))
Esempio n. 54
0
def explode(bot, trigger):
    target = Identifier(trigger.group(3))
    kmsg = 'KICK ' + trigger.sender + ' ' + target + \
           ' : Oh, come on, ' + target + '! You could\'ve at least picked one! Now you\'re dead. Guts, all over the place. You see that? Guts, all over YourPants. (You should\'ve picked the ' + bombs[target.lower()][0] + ' wire.)'
    bot.write([kmsg])
    bombs.pop(target.lower())
Esempio n. 55
0
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
Esempio n. 56
0
def start(bot, trigger):
    """
    Put a bomb in the specified user's pants. If they take too long or guess wrong,
     they die (and get kicked from the channel, if enabled).
    """
    if not trigger.group(3):
        bot.say(STRINGS['TARGET_MISSING'])
        return NOLIMIT
    if bot.db.get_channel_value(trigger.sender, 'bombs_disabled'):
        bot.notice(STRINGS['CHANNEL_DISABLED'] % trigger.sender, trigger.nick)
        return NOLIMIT
    since_last = time_since_bomb(bot, trigger.nick)
    if since_last < TIMEOUT and not trigger.admin:
        bot.notice(STRINGS['TIMEOUT_REMAINING'] % (TIMEOUT - since_last),
                   trigger.nick)
        return
    global BOMBS
    target = Identifier(trigger.group(3))
    target_unbombable = bot.db.get_nick_value(target, 'unbombable')
    if target == bot.nick:
        bot.say(STRINGS['TARGET_BOT'])
        return NOLIMIT
    if is_self(bot, trigger.nick, target):
        bot.say(STRINGS['TARGET_SELF'] % trigger.nick)
        return NOLIMIT
    if target.lower() not in bot.privileges[trigger.sender.lower()]:
        bot.say(STRINGS['TARGET_IMAGINARY'])
        return NOLIMIT
    if target_unbombable and not trigger.admin:
        bot.say(STRINGS['TARGET_DISABLED'] % target)
        return NOLIMIT
    if bot.db.get_nick_value(trigger.nick, 'unbombable'):
        bot.say(STRINGS['NOT_WHILE_DISABLED'] % trigger.nick)
        return NOLIMIT
    with lock:
        if target.lower() in BOMBS:
            bot.say(STRINGS['TARGET_FULL'] % target)
            return NOLIMIT
        wires = [COLORS[i] for i in sorted(sample(xrange(len(COLORS)), randrange(3, 5)))]
        num_wires = len(wires)
        wires_list = [formatting.color(str(wire), str(wire)) for wire in wires]
        wires_list = ", ".join(wires_list[:-2] + [" and ".join(wires_list[-2:])]).replace('Light_', '')
        wires = [wire.replace('Light_', '') for wire in wires]
        color = choice(wires)
        bot.say(
                choice(STRINGS['BOMB_PLANTED']) % {'target':           target,
                                                   'fuse_time': STRINGS['FUSE'],
                                                   'wire_num':         num_wires,
                                                   'wire_list':        wires_list,
                                                   'prefix':           bot.config.core.help_prefix or '.'
                                                   })
        bot.notice(STRINGS['BOMB_ANSWER'] % (target, color), trigger.nick)
        if target_unbombable:
            bot.notice(STRINGS['TARGET_DISABLED_FYI'] % target, trigger.nick)
        timer = Timer(FUSE, explode, (bot, trigger))
        BOMBS[target.lower()] = {'wires':  wires,
                                 'color':  color,
                                 'timer':  timer,
                                 'target': target,
                                 'bomber': trigger.nick
                                 }
        timer.start()
    bombs_planted = bot.db.get_nick_value(trigger.nick, 'bombs_planted') or 0
    bot.db.set_nick_value(trigger.nick, 'bombs_planted', bombs_planted + 1)
    bot.db.set_nick_value(trigger.nick, 'bomb_last_planted', time.time())