Exemplo n.º 1
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][trigger.nick] < OP:
        return
    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])
Exemplo n.º 2
0
def collectlines(bot, trigger):
    """Create a temporary log of what people say"""

    # Don't log things in PM
    if trigger.is_privmsg:
        return

    # Add a log for the channel and nick, if there isn't already one
    if trigger.sender not in bot.memory['find_lines']:
        bot.memory['find_lines'][trigger.sender] = lpbotMemory()
    if Identifier(
            trigger.nick) not in bot.memory['find_lines'][trigger.sender]:
        bot.memory['find_lines'][trigger.sender][Identifier(
            trigger.nick)] = list()

    # Create a temporary list of the user's lines in a channel
    templist = bot.memory['find_lines'][trigger.sender][Identifier(
        trigger.nick)]
    line = trigger.group()
    if line.startswith("s/"):  # Don't remember substitutions
        return
    elif line.startswith("\x01ACTION"):  # For /me messages
        line = line[:-1]
        templist.append(line)
    else:
        templist.append(line)

    del templist[:-10]  # Keep the log to 10 lines per person

    bot.memory['find_lines'][trigger.sender][Identifier(
        trigger.nick)] = templist
Exemplo n.º 3
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][trigger.nick] < OP:
        return
    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', opt, '-q', quietmask])
Exemplo n.º 4
0
def kick(bot, trigger):
    """
    Kick a user from the channel.
    """
    if bot.privileges[trigger.sender][trigger.nick] < OP:
        return
    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.nick:
        bot.write(['KICK', channel, nick, reason])
Exemplo n.º 5
0
def test_get_nick_id(db):
    conn = sqlite3.connect(db_filename)
    tests = [
        [None, 'embolalia', Identifier('Embolalia')],
        # Ensures case conversion is handled properly
        [None, '[][]', Identifier('[]{}')],
        # Unicode, just in case
        [None, 'embölaliå', Identifier('EmbölaliÅ')],
    ]

    for test in tests:
        test[0] = db.get_nick_id(test[2])
        nick_id, slug, nick = test
        with conn:
            cursor = conn.cursor()
            registered = cursor.execute(
                'SELECT nick_id, slug, canonical FROM nicknames WHERE canonical IS ?',
                [nick]).fetchall()
            assert len(registered) == 1
            assert registered[0][1] == slug and registered[0][2] == nick

    # Check that each nick ended up with a different id
    assert len(set(test[0] for test in tests)) == len(tests)

    # Check that the retrieval actually is idempotent
    for test in tests:
        nick_id = test[0]
        new_id = db.get_nick_id(test[2])
        assert nick_id == new_id

    # Even if the case is different
    for test in tests:
        nick_id = test[0]
        new_id = db.get_nick_id(Identifier(test[2].upper()))
        assert nick_id == new_id
Exemplo n.º 6
0
def ban(bot, trigger):
    """
    This give admins the ability to ban a user.
    The bot must be a Channel Operator for this command to work.
    """
    if bot.privileges[trigger.sender][trigger.nick] < OP:
        return
    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])
    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])
Exemplo n.º 7
0
def kick(bot, trigger):
    """
    Kick a user from the channel.
    """
    if bot.privileges[trigger.sender][trigger.nick] < OP:
        return
    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.nick:
        bot.write(['KICK', channel, nick, reason])
Exemplo n.º 8
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][trigger.nick] < OP:
        return
    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])
Exemplo n.º 9
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][trigger.nick] < OP:
        return
    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', opt, '-q', quietmask])
Exemplo n.º 10
0
def ban(bot, trigger):
    """
    This give admins the ability to ban a user.
    The bot must be a Channel Operator for this command to work.
    """
    if bot.privileges[trigger.sender][trigger.nick] < OP:
        return
    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])
    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])
Exemplo n.º 11
0
def f_remind(bot, trigger):
    """Give someone a message the next time they're seen"""
    teller = trigger.nick
    verb = trigger.group(1)

    if not trigger.group(3):
        bot.reply("{} whom?".format(verb))
        return

    tellee = trigger.group(3).rstrip('.,:;')
    msg = trigger.group(2).lstrip(tellee).lstrip()

    if not msg:
        bot.reply("{} {} what?".format(verb, tellee))
        return

    tellee = Identifier(tellee)

    if not os.path.exists(bot.tell_filename):
        return

    if len(tellee) > 20:
        return bot.reply('That nickname is too long.')
    if tellee == bot.nick:
        return bot.reply("I'm here now, you can tell me whatever you want!")

    if not tellee in (Identifier(teller), bot.nick, 'me'):
        tz = get_timezone(bot.db, bot.config, None, tellee)
        timenow = format_time(bot.db,
                              bot.config,
                              tz,
                              tellee,
                              channel=trigger.sender)
        bot.memory['tell_lock'].acquire()
        try:
            if not tellee in bot.memory['reminders']:
                bot.memory['reminders'][tellee] = [(teller, verb, timenow, msg)
                                                   ]
            else:
                bot.memory['reminders'][tellee].append(
                    (teller, verb, timenow, msg))
        finally:
            bot.memory['tell_lock'].release()

        response = "I'll pass that on when {} is around.".format(tellee)

        bot.reply(response)
    elif Identifier(teller) == tellee:
        bot.say('You can {} yourself that.'.format(verb))
    else:
        bot.say("Hey, I'm not as stupid as Monty you know!")

    dumpReminders(bot.tell_filename, bot.memory['reminders'],
                  bot.memory['tell_lock'])  # @@ tell
Exemplo n.º 12
0
def test_merge_nick_groups(db):
    conn = sqlite3.connect(db_filename)
    aliases = ['Embolalia', 'Embo']
    for nick_id, alias in enumerate(aliases):
        conn.execute('INSERT INTO nicknames VALUES (?, ?, ?)',
                     [nick_id, Identifier(alias).lower(), alias])
    conn.commit()

    finals = (('foo', 'bar'), ('bar', 'blue'), ('spam', 'eggs'))

    db.set_nick_value(aliases[0], finals[0][0], finals[0][1])
    db.set_nick_value(aliases[0], finals[1][0], finals[1][1])
    db.set_nick_value(aliases[1], 'foo', 'baz')
    db.set_nick_value(aliases[1], finals[2][0], finals[2][1])

    db.merge_nick_groups(aliases[0], aliases[1])

    nick_id = conn.execute('SELECT nick_id FROM nicknames').fetchone()[0]
    alias_id = conn.execute('SELECT nick_id FROM nicknames').fetchone()[0]
    assert nick_id == alias_id

    for key, value in finals:
        found = conn.execute(
            'SELECT value FROM nick_values WHERE nick_id = ? AND key = ?',
            [nick_id, key]).fetchone()[0]
        assert json.loads(unicode(found)) == value
Exemplo n.º 13
0
Arquivo: irc.py Projeto: nikolak/lpbot
    def __init__(self, config):
        ca_certs = '/etc/pki/tls/cert.pem'
        if config.ca_certs is not None:
            ca_certs = config.ca_certs
        elif not os.path.isfile(ca_certs):
            ca_certs = '/etc/ssl/certs/ca-certificates.crt'
        if not os.path.isfile(ca_certs):
            stderr('Could not open CA certificates file. SSL will not '
                   'work properly.')

        if config.log_raw is None:
            # Default is to log raw data, can be disabled in config
            config.log_raw = True
        asynchat.async_chat.__init__(self)
        self.set_terminator(b'\n')
        self.buffer = ''

        self.nick = Identifier(config.nick)
        """lpbot's current ``Identifier``. Changing this while lpbot is running is
        untested."""
        self.user = config.user
        """lpbot's user/ident."""
        self.name = config.name
        """lpbot's "real name", as used for whois."""

        self.channels = []
        """The list of channels lpbot is currently in."""

        self.stack = {}
        self.ca_certs = ca_certs
        self.hasquit = False

        self.sending = threading.RLock()
        self.writing_lock = threading.Lock()
        self.raw = None

        # Right now, only accounting for two op levels.
        # This might be expanded later.
        # These lists are filled in startup.py, as of right now.
        self.ops = dict()
        """
        A dictionary mapping channels to a ``Identifier`` list of their operators.
        """
        self.halfplus = dict()
        """
        A dictionary mapping channels to a ``Identifier`` list of their half-ops and
        ops.
        """
        self.voices = dict()
        """
        A dictionary mapping channels to a ``Identifier`` list of their voices,
        half-ops and ops.
        """

        # We need this to prevent error loops in handle_error
        self.error_count = 0

        self.connection_registered = False
        """ Set to True when a server has accepted the client connection and
Exemplo n.º 14
0
def track_nicks(bot, trigger):
    """Track nickname changes and maintain our chanops list accordingly."""
    old = trigger.nick
    new = Identifier(trigger)

    if old == bot.config.core.owner:
        bot.memory['owner_auth'] = False

    # Give debug mssage, and PM the owner, if the bot's own nick changes.
    if old == 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

    # Old privilege maintenance
    for channel in bot.halfplus:
        if old in bot.halfplus[channel]:
            bot.del_halfop(channel, old)
            bot.add_halfop(channel, new)
    for channel in bot.ops:
        if old in bot.ops[channel]:
            bot.del_op(channel, old)
            bot.add_op(channel, new)
    for channel in bot.voices:
        if old in bot.voices[channel]:
            bot.del_voice(channel, old)
            bot.add_voice(channel, new)
Exemplo n.º 15
0
def test_unalias_nick(db):
    conn = sqlite3.connect(db_filename)
    nick = 'Embolalia'
    nick_id = 42
    conn.execute('INSERT INTO nicknames VALUES (?, ?, ?)',
                 [nick_id, Identifier(nick).lower(), nick])
    aliases = ['EmbölaliÅ', 'Embo`work', 'Embo']
    for alias in aliases:
        conn.execute('INSERT INTO nicknames VALUES (?, ?, ?)',
                     [nick_id, Identifier(alias).lower(), alias])
    conn.commit()

    for alias in aliases:
        db.unalias_nick(alias)

    for alias in aliases:
        found = conn.execute('SELECT * FROM nicknames WHERE nick_id = ?',
                             [nick_id]).fetchall()
        assert len(found) == 1
Exemplo n.º 16
0
 def _nick_blocked(self, nick):
     bad_nicks = self.config.core.get_list('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
Exemplo n.º 17
0
def handle_names(bot, trigger):
    """Handle NAMES response, happens when joining to channels."""
    names = trigger.split()

    channels = re.search('(#\S*)', trigger.raw)
    if not channels:
        return
    channel = Identifier(channels.group(1))
    if channel not in bot.privileges:
        bot.privileges[channel] = dict()
    bot.init_ops_list(channel)

    # This could probably be made flexible in the future, but I don't think
    # it'd be worth it.
    mapping = {
        '+': lpbot.module.VOICE,
        '%': lpbot.module.HALFOP,
        '@': lpbot.module.OP,
        '&': lpbot.module.ADMIN,
        '~': lpbot.module.OWNER
    }

    for name in names:
        priv = 0
        for prefix, value in iteritems(mapping):
            if prefix in name:
                priv = priv | value
        nick = Identifier(name.lstrip(''.join(mapping.keys())))
        bot.privileges[channel][nick] = priv

        # Old op list maintenance is down here, and should be removed at some
        # point
        if '@' in name or '~' in name or '&' in name:
            bot.add_op(channel, name.lstrip('@&%+~'))
            bot.add_halfop(channel, name.lstrip('@&%+~'))
            bot.add_voice(channel, name.lstrip('@&%+~'))
        elif '%' in name:
            bot.add_halfop(channel, name.lstrip('@&%+~'))
            bot.add_voice(channel, name.lstrip('@&%+~'))
        elif '+' in name:
            bot.add_voice(channel, name.lstrip('@&%+~'))
Exemplo n.º 18
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
        if trigger.nick == bot.config.core.owner:
            bot.memory['owner_auth'] = False
        try:
            del bot.privileges[trigger.sender][nick]
        except KeyError:
            pass
Exemplo n.º 19
0
def test_delete_nick_group(db):
    conn = sqlite3.connect(db_filename)
    aliases = ['Embolalia', 'Embo']
    nick_id = 42
    for alias in aliases:
        conn.execute('INSERT INTO nicknames VALUES (?, ?, ?)',
                     [nick_id, Identifier(alias).lower(), alias])
    conn.commit()

    db.set_nick_value(aliases[0], 'foo', 'bar')
    db.set_nick_value(aliases[1], 'spam', 'eggs')

    db.delete_nick_group(aliases[0])

    # Nothing else has created values, so we know the tables are empty
    nicks = conn.execute('SELECT * FROM nicknames').fetchall()
    assert len(nicks) == 0
    data = conn.execute('SELECT * FROM nick_values').fetchone()
    assert data is None
Exemplo n.º 20
0
def seen(bot, trigger):
    """Reports when and where the user was last seen."""
    if not trigger.group(2):
        bot.say(".seen <nick> - Reports when <nick> was last seen.")
        return
    nick = Identifier(trigger.group(2).strip())
    if nick in seen_dict:
        timestamp = seen_dict[nick]['timestamp']
        channel = seen_dict[nick]['channel']
        message = seen_dict[nick]['message']

        tz = get_timezone(bot.db, bot.config, None, trigger.nick,
                          trigger.sender)
        saw = datetime.datetime.utcfromtimestamp(timestamp)
        timestamp = format_time(bot.db, bot.config, tz, trigger.nick,
                                trigger.sender, saw)

        msg = "I last saw %s at %s on %s, saying \"%s\"" % (nick, timestamp,
                                                            channel, message)
        bot.say(str(trigger.nick) + ': ' + msg)
    else:
        bot.say("Sorry, I haven't seen %s around." % nick)
Exemplo n.º 21
0
Arquivo: irc.py Projeto: nikolak/lpbot
 def del_halfop(self, channel, name):
     self.halfplus[channel].discard(Identifier(name))
Exemplo n.º 22
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

    def handle_old_modes(nick, mode):
        #Old mode maintenance. Drop this crap in 5.0.
        if mode[1] == 'o' or mode[1] == 'q' or mode[1] == 'a':
            if mode[0] == '+':
                bot.add_op(channel, nick)
            else:
                bot.del_op(channel, nick)
        elif mode[1] == 'h':  # Halfop
            if mode[0] == '+':
                bot.add_halfop(channel, nick)
            else:
                bot.del_halfop(channel, nick)
        elif mode[1] == 'v':
            if mode[0] == '+':
                bot.add_voice(channel, nick)
            else:
                bot.del_voice(channel, nick)

    mapping = {'v': lpbot.module.VOICE,
               'h': lpbot.module.HALFOP,
               'o': lpbot.module.OP,
               'a': lpbot.module.ADMIN,
               'q': lpbot.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
                handle_old_modes(arg, mode)
Exemplo n.º 23
0
def findandreplace(bot, trigger):
    # Don't bother in PM
    if trigger.is_privmsg:
        return

    # Correcting other person vs self.
    rnick = Identifier(trigger.group(1) or trigger.nick)

    search_dict = bot.memory['find_lines']
    # only do something if there is conversation to work with
    if trigger.sender not in search_dict:
        return
    if Identifier(rnick) not in search_dict[trigger.sender]:
        return

    #TODO rest[0] is find, rest[1] is replace. These should be made variables of
    #their own at some point.
    rest = [trigger.group(2), trigger.group(3)]
    rest[0] = rest[0].replace(r'\/', '/')
    rest[1] = rest[1].replace(r'\/', '/')
    me = False  # /me command
    flags = (trigger.group(4) or '')

    # If g flag is given, replace all. Otherwise, replace once.
    if 'g' in flags:
        count = -1
    else:
        count = 1

    # repl is a lambda function which performs the substitution. i flag turns
    # off case sensitivity. re.U turns on unicode replacement.
    if 'i' in flags:
        regex = re.compile(re.escape(rest[0]), re.U | re.I)
        repl = lambda s: re.sub(regex, rest[1], s, count == 1)
    else:
        repl = lambda s: s.replace(rest[0], rest[1], count)

    # Look back through the user's lines in the channel until you find a line
    # where the replacement works
    for line in reversed(search_dict[trigger.sender][rnick]):
        if line.startswith("\x01ACTION"):
            me = True  # /me command
            line = line[8:]
        else:
            me = False
        new_phrase = repl(line)
        if new_phrase != line:  # we are done
            break

    if not new_phrase or new_phrase == line:
        return  # Didn't find anything

    # Save the new "edited" message.
    action = (me and '\x01ACTION ') or ''  # If /me message, prepend \x01ACTION
    templist = search_dict[trigger.sender][rnick]
    templist.append(action + new_phrase)
    search_dict[trigger.sender][rnick] = templist
    bot.memory['find_lines'] = search_dict

    # output
    if not me:
        new_phrase = '%s to say: %s' % (bold('meant'), new_phrase)
    if trigger.group(1):
        phrase = '%s thinks %s %s' % (trigger.nick, rnick, new_phrase)
    else:
        phrase = '%s %s' % (trigger.nick, new_phrase)

    bot.say(phrase)
Exemplo n.º 24
0
Arquivo: irc.py Projeto: nikolak/lpbot
 def add_halfop(self, channel, name):
     if isinstance(name, Identifier):
         self.halfplus[channel].add(name)
     else:
         self.halfplus[channel].add(Identifier(name))
Exemplo n.º 25
0
Arquivo: irc.py Projeto: nikolak/lpbot
    def msg(self, recipient, text, max_messages=1):
        #TODO: it is not obvious how self.stack works,
        #add explanation or make code more self-documenting

        # We're arbitrarily saying that the max is 400 bytes of text when
        # messages will be split. Otherwise, we'd have to acocunt for the bot's
        # hostmask, which is hard.
        max_text_length = 400
        # Encode to bytes, for proper length calculation
        if isinstance(text, str):
            encoded_text = text.encode('utf-8')
        else:
            encoded_text = text
        excess = ''
        if max_messages > 1 and len(encoded_text) > max_text_length:
            last_space = encoded_text.rfind(' '.encode('utf-8'), 0,
                                            max_text_length)
            if last_space == -1:
                excess = encoded_text[max_text_length:]
                encoded_text = encoded_text[:max_text_length]
            else:
                excess = encoded_text[last_space + 1:]
                encoded_text = encoded_text[:last_space]
        # We'll then send the excess at the end
        # Back to str again, so we don't screw things up later.
        text = encoded_text.decode('utf-8')
        try:
            self.sending.acquire()

            # No messages within the last 3 seconds? Go ahead!
            # Otherwise, wait so it's been at least 0.8 seconds + penalty

            recipient_id = Identifier(recipient)

            if recipient_id not in self.stack:
                self.stack[recipient_id] = []
            elif self.stack[recipient_id]:
                elapsed = time.time() - self.stack[recipient_id][-1][0]
                if elapsed < 3:
                    penalty = float(max(0, len(text) - 50)) / 70
                    wait = 0.7 + penalty
                    if elapsed < wait:
                        time.sleep(wait - elapsed)

                # Loop detection
                messages = [m[1] for m in self.stack[recipient_id][-8:]]

                # If what we are about to send repeated at least 5 times in the
                # last 2 minutes, replace with '...'
                if messages.count(text) >= 5 and elapsed < 120:
                    text = '...'
                    if messages.count('...') >= 3:
                        # If we said '...' 3 times, discard message
                        return

            self.write(('PRIVMSG', recipient), text)
            self.stack[recipient_id].append((time.time(), self.safe(text)))
            self.stack[recipient_id] = self.stack[recipient_id][-10:]
        finally:
            self.sending.release()
        # Now that we've sent the first part, we need to send the rest. Doing
        # this recursively seems easier to me than iteratively
        if excess:
            self.msg(recipient, excess, max_messages - 1)
Exemplo n.º 26
0
Arquivo: irc.py Projeto: nikolak/lpbot
 def add_voice(self, channel, name):
     if isinstance(name, Identifier):
         self.voices[channel].add(name)
     else:
         self.voices[channel].add(Identifier(name))
Exemplo n.º 27
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

    def handle_old_modes(nick, mode):
        #Old mode maintenance. Drop this crap in 5.0.
        if mode[1] == 'o' or mode[1] == 'q' or mode[1] == 'a':
            if mode[0] == '+':
                bot.add_op(channel, nick)
            else:
                bot.del_op(channel, nick)
        elif mode[1] == 'h':  # Halfop
            if mode[0] == '+':
                bot.add_halfop(channel, nick)
            else:
                bot.del_halfop(channel, nick)
        elif mode[1] == 'v':
            if mode[0] == '+':
                bot.add_voice(channel, nick)
            else:
                bot.del_voice(channel, nick)

    mapping = {
        'v': lpbot.module.VOICE,
        'h': lpbot.module.HALFOP,
        'o': lpbot.module.OP,
        'a': lpbot.module.ADMIN,
        'q': lpbot.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
                handle_old_modes(arg, mode)
Exemplo n.º 28
0
Arquivo: irc.py Projeto: nikolak/lpbot
 def del_op(self, channel, name):
     self.ops[channel].discard(Identifier(name))
Exemplo n.º 29
0
def note(bot, trigger):
    if not trigger.is_privmsg:
        nick = Identifier(trigger.nick)
        seen_dict[nick]['timestamp'] = time.time()
        seen_dict[nick]['channel'] = trigger.sender
        seen_dict[nick]['message'] = trigger
Exemplo n.º 30
0
Arquivo: irc.py Projeto: nikolak/lpbot
 def del_voice(self, channel, name):
     self.voices[channel].discard(Identifier(name))