from datetime import datetime, timedelta
import random
import re

class Message:
    def __init__(self, sent, msg):
        self.sent = sent
        self.msg = msg
    def replyFormat(self):
        return 'From {user}: {msg}'.format(user = self.sent, msg = self.msg)

class MessageDatabase:
    # Constant
    def NOTIFICATION_GAP(self): return timedelta(hours = 1)

    def __init__(self):
        self.messages = {}
        self.lastNotification = {}
    def pendingMessages(self, user):
        cnt = len(self.messages[user])
        return 'You have {nr} message{s} waiting for you.\nUse ~read [number] to get [number] of messages shown to you'.format(nr = cnt, s = 's' if cnt > 1 else '')
    def addMessage(self, to, sent, msg):
        if to not in self.messages: self.messages[to] = {}
        # Set last notification to something far in the past so it triggers the first time always
        if to not in self.lastNotification: self.lastNotification[to] = datetime(2015, 1, 1)
        if sent in self.messages[to]: return False

        self.messages[to][bot.toId(sent)] = Message(sent, msg)
        return True

    def getMessage(self, user):
        return self.removeRandomMessage(user).replyFormat()
    def getMessages(self, user, amnt):
        ''' This removes amnt number of messages from the message service '''

        # This can be super-spammy for users with a lot of pending messages
        # as they can opt to look at all at once
        reply = ''
        if amnt > len(self.messages[user]): amnt = len(self.messages[user])
        while amnt > 0:
            reply += self.getMessage(user) + ('\n' if amnt > 1 else '')
            amnt -= 1
        # Remove the user from the list if there's no messages left
        # and clear the last notification time
        if not self.messages[user]:
            self.messages.pop(user)
            self.lastNotification.pop(user)
        return reply

    def getAllMessages(self, user):
        ''' This gets and delete every message to this user from storage '''

        # No need to test for existance, this assumes a message exists
        # and usage should first test for existance.
        messages = self.removeAllMessages(user)
        combine = []
        for msg in messages:
            combine.append(messages[msg].replyFormat())
        return '\n'.join(combine)

    def shouldNotifyMessage(self, user):
        if self.hasMessage(user):
            now = datetime.now()
            if now - self.lastNotification[user] > self.NOTIFICATION_GAP():
                self.lastNotification[user] = now
                return True
        return False

    def hasMessage(self, user):
        return user in self.messages

    def alreadySentMessage(self, user, frm):
        return user in self.messages and frm in self.messages[user]

    def removeRandomMessage(self, to):
        return self.messages[to].pop(random.choice(list(self.messages[to].keys())), None)
    def removeMessage(self, to, frm):
        msg = self.messages[to].pop(frm, None)
        # If the user has no message left, clear the name entry
        if len(self.messages[to]) < 1: self.messages.pop(to)
        return msg

    # Unused but still supported
    def removeAllMessages(self, to):
        return self.messages.pop(to, None)

def commands(bot, cmd, room, msg, user):
    notes = bot.usernotes
    if cmd == 'tell':
        if not msg: return 'You need to specify a user and a message to send in the format: [user], [message]', False
        msg = msg.split(',')
        to = bot.toId(msg[0])
        if notes.alreadySentMessage(to, user.id): return 'You already have a message to this user waiting', False
        if len(msg) < 2: return 'You forgot a message', True
        if len(msg[1].lstrip()) > 150: return 'Message is too long. Max limit is 150 characters', False
        notes.addMessage(to, user.name, msg[1].lstrip())
        return "I'll be sure to tell {user} that.".format(user = to), True
    # Getting the messages back
    elif cmd == 'read':
        if not notes.hasMessage(user.id): return 'You have no messages waiting', False
        if not msg:
            # If the user didn't speify any amount to return, give back a single message
            return notes.getMessages(user.id, 1), False
        if not msg.isdigit() and int(msg) < 1: return 'Please enter a whole, positive number', False
        return notes.getMessages(user.id, int(msg)), False
    elif cmd == 'untell':
        if not msg: return 'You need to specify a user to remove', False
        if not notes.hasMessage(msg): return 'This user has no waiting messages', False
        if not notes.removeMessage(msg, user.id): return 'You have no message to this user waiting', False
        return 'Message removed', True
    return '', False
@@ -1061,21 +1061,49 @@
 to%5D%5B
-bot.toId(sent
+re.sub(r'%5B%5Ea-zA-z0-9,%5D', '', sent).lower(
 )%5D =

Fix crash in messages