예제 #1
0
 def __init__(self,
              creator,
              server,
              name=None,
              description=None,
              openTime=None,
              closeTime=None,
              absoluteThreshold=None,
              percentThreshold=None,
              percentThresholdMinimum=None,
              thresholdTime=None,
              keepUpdated=True,
              pollid=None):
     self.log = Logger()
     self.base = CabbageBase()
     self.creator = creator
     self.server = server
     self.name = name
     self.description = description
     self.openTime = openTime
     self.closeTime = closeTime
     self.absoluteThreshold = absoluteThreshold
     self.percentThreshold = percentThreshold
     self.percentThresholdMinimum = percentThresholdMinimum
     self.thresholdTime = thresholdTime
     self.options = {'short': [], 'long': [], 'emoji': []}
     self.keepUpdated = keepUpdated
     if pollid:
         self.pollid = pollid
     else:
         self.genPollid()
     self.update()
예제 #2
0
class Logger:
    ''' Functions for writing log data to the SQL database 
	  ' Note on logging verbosity levels:
		'  1 -- critical program error/crash
		'  2 -- program error or warning
		'  3 -- extra-high-importance
		'  4 -- high-importance (e.g. admin promotion/demotion)
		'  5 -- standard importance
		'  6 -- low importance (new poll tracking messages)
		'  7 -- extra-low importance
	  '''
    def __init__(self):
        self.base = CabbageBase()

    def log(self, message, module='Core', verbosity=5):
        ''' Logs a message (with optional module id) to the database '''
        cmd = 'INSERT INTO log (time, message, module, verbosity) VALUES (%s,%s,%s,%s)'
        cur = self.base.getCursor()
        cur.execute(cmd, (datetime.now(), message, module, verbosity))
        self.base.commit()
        cur.close()
예제 #3
0
 def __init__(self, bot):
     self.bot = bot
     self.base = DB()
     self.saved = None
예제 #4
0
class Poll:
    ''' Polling module '''
    def __init__(self, bot):
        self.bot = bot
        self.base = DB()
        self.saved = None

    async def on_reaction_add(self, reaction, user):
        if user == self.bot.user:
            return
        res = self.base.query('activemessages', ('pollid', ),
                              (('messid', int(reaction.message.id)), ))
        if len(res) > 0:
            pollid = res[0][0]
            poll = PollFramework.fromSQL(
                self.base.query('polls',
                                filters=(('pollid', int(pollid)), ))[0])
            resp = poll.voteEmoji(user.id, reaction.emoji)
            await self.updatePoll(pollid)

    @commands.command(pass_context=True)
    async def react(self, ctx, *emojis):
        print(emojis)
        for emoji in emojis:
            if re.match('<:.+:[0-9]+>', emoji):
                # Custom emoji -- parse it as such
                await self.bot.add_reaction(ctx.message, emoji[1:-1])
            else:
                # Maybe a unicode emoji -- try it, I guess
                await self.bot.add_reaction(ctx.message, emoji[0])

    @commands.command(pass_context=True)
    async def saveme(self, ctx):
        p = Phrasebook(ctx, self.bot)
        await self.bot.say(p.pickPhrase('poll', 'saveme'))

    @commands.command(pass_context=True)
    async def wakemeup(self, ctx):
        p = Phrasebook(ctx, self.bot)
        await self.bot.say(p.pickPhrase('poll', 'wakemeup'))

    @commands.group(invoke_without_command=True)
    async def poll(self):
        await self.bot.say('Test')

    @poll.command(pass_context=True)
    async def test1(self, ctx):
        if ctx.message.server:
            f = PollFramework(ctx.message.author.id, ctx.message.server.id,
                              ctx.message.channel.id, ctx.message.id)
        else:
            f = PollFramework(ctx.message.author.id, None,
                              ctx.message.channel.id, ctx.message.id)
        f.setName('Motion to Test The Poll Module')
        f.setDescription(
            'Approval or rejection of this motion will have tested the Poll module, so feel free to do either (or try to do both!)'
        )
        f.setOpenTime(datetime.now())
        f.setCloseTime(datetime.now() + timedelta(seconds=30))
        f.setAbsoluteThreshold(10)
        f.setPercentThreshold(0.80)
        f.setPercentThresholdMinimum(5)
        f.setThresholdTime(datetime.now() + timedelta(seconds=10))
        f.addOption('Support', 'Support the addition of the polling module',
                    '✅')
        f.addOption('Oppose', 'Oppose the addition of the polling module', '❌')
        f.addOption('Woah', 'Woah', '<:woah:382379019993350145>')
        mess = await self.render(f)
        f.addTrackingMessage(mess.id, mess.channel.id)

    @poll.command(pass_context=True)
    async def create(self, ctx, name, description):
        if ctx.message.server:
            f = PollFramework(ctx.message.author.id, ctx.message.server.id,
                              name, description)
        else:
            f = PollFramework(ctx.message.author.id, None, name, description)
        await self.bot.say('Created a new poll with id ' +
                           str(f.get()['pollid']))

    @poll.group()
    async def set(self):
        pass

    @set.command(pass_context=True)
    async def name(self, ctx, name, pollid=None):
        poll = await self.lookupPoll(ctx, pollid)
        if not poll:
            return

        poll.setName(name)
        await self.updatePoll(pollid)

    @set.command(pass_context=True)
    async def description(self, ctx, desc, pollid=None):
        poll = await self.lookupPoll(ctx, pollid)
        if not poll:
            return

        poll.setDescription(desc)
        await self.updatePoll(pollid)

    @set.command(pass_context=True)
    async def numThreshold(self, ctx, thresh, pollid=None):
        poll = await self.lookupPoll(ctx, pollid)
        if not poll:
            return

        poll.setAbsoluteThreshold(thresh)
        await self.updatePoll(pollid)

    @set.command(pass_context=True)
    async def percThreshold(self, ctx, thresh, minimum=1, pollid=None):
        poll = await self.lookupPoll(ctx, pollid)
        if not poll:
            return

        poll.setPercentThreshold(thresh)
        poll.setPercentThresholdMinimum(minimum)
        await self.updatePoll(pollid)

    @poll.command(pass_context=True)
    async def addResp(self, ctx, short, med, emoji=None, pollid=None):
        poll = await self.lookupPoll(ctx, pollid)
        if not poll:
            return

        poll.addOption(short, med, emoji)
        await self.updatePoll(pollid)

    @poll.command(pass_context=True)
    async def open(self, ctx, closeTime=1, closeIncrement='day', pollid=None):
        poll = await self.lookupPoll(ctx, pollid)
        if not poll:
            return

        now = datetime.now()
        closeIncrement = closeIncrement.lower()
        if closeIncrement == 'second' or closeIncrement == 'seconds':
            t = timedelta(seconds=int(closeTime))
        elif closeIncrement == 'minute' or closeIncrement == 'minutes':
            t = timedelta(minutes=int(closeTime))
        elif closeIncrement == 'hour' or closeIncrement == 'hours':
            t = timedelta(hours=int(closeTime))
        elif closeIncrement == 'day' or closeIncrement == 'days':
            t = timedelta(days=int(closeTime))
        elif closeIncrement == 'week' or closeIncrement == 'weeks':
            t = timedelta(weeks=int(closeTime))
        elif closeIncrement == 'month' or closeIncrement == 'months':
            t = timedelta(months=int(closeTime))
        elif closeIncrement == 'year' or closeIncrement == 'years':
            t = timedelta(years=int(closeTime))
        else:
            await self.bot.say('Unknown time interval "' + closeIncrement +
                               '"')

        closeTime = now + t
        poll.setOpenTime(now)
        poll.setCloseTime(closeTime)
        await self.updatePoll(pollid)
        print('test')

    @poll.command(pass_context=True)
    async def vote(self, ctx, vote, pollid=None):
        if not pollid:
            res = await self.autoGuessPollid(ctx)
            if res:
                pollid = res
            else:
                return

        poll = PollFramework.fromSQL(
            self.base.query('polls', filters=(('pollid', int(pollid)), ))[0])
        toDel = None
        resp = poll.vote(ctx.message.author.id, vote)
        if resp == 0:
            toDel = await self.bot.say('Successfully voted on poll ID ' +
                                       str(pollid))
        elif resp == 1:
            toDel = await self.bot.say('Invalid option "' + vote +
                                       '" for poll ID ' + str(pollid) + '!')
        elif resp == 2:
            toDel = await self.bot.say('Poll ID ' + str(pollid) +
                                       ' is not open!')

        mark = datetime.now()
        await self.updatePoll(pollid)
        while datetime.now() - mark < timedelta(seconds=5):
            pass

        await self.bot.delete_message(toDel)
        await self.bot.delete_message(ctx.message)

    @poll.command()
    async def put(self, pollid):
        poll = PollFramework.fromSQL(
            self.base.query('polls', filters=(('pollid', int(pollid)), ))[0])
        mess = await self.render(poll)
        poll.addTrackingMessage(mess.id, mess.channel.id)

    @poll.command(pass_context=True)
    async def update(self, ctx, single=None):
        await self.updatePoll(single)

    async def updatePoll(self, single=None):
        if single:
            res = self.base.query('activemessages',
                                  ('messid', 'chanid', 'pollid'),
                                  (('pollid', int(single)), ))
        else:
            res = self.base.query('activemessages',
                                  ('messid', 'chanid', 'pollid'))
        for active in res:
            print('Attempting to update poll with id ' + str(active[2]))
            try:
                chan = self.bot.get_channel(str(active[1]))
                if not chan:
                    raise TypeError
                mess = await self.bot.get_message(chan, str(active[0]))
                poll = PollFramework.fromSQL(
                    self.base.query('polls',
                                    filters=(('pollid', active[2]), ))[0])
                await self.render(poll, mess)
            except (discord.errors.NotFound, TypeError):
                rc.pwarn('Message id ' + str(active[0]) + ' on channel id ' +
                         str(active[1]) + ' no longer valid! Deleting.')
                cur = self.base.getCursor()
                cmd = 'DELETE FROM activemessages WHERE messid=%s AND chanid=%s'
                cur.execute(cmd, (active[0], active[1]))
                self.base.commit()
                cur.close()

    async def lookupPoll(self, ctx, pollid=None):
        if not pollid:
            pollid = await self.autoGuessPollid(ctx)
            if not pollid:
                return None
        poll = PollFramework.fromSQL(
            self.base.query('polls', filters=(('pollid', int(pollid)), ))[0])
        return poll

    async def autoGuessPollid(self, ctx):
        res = self.guessPollid(ctx)
        if res[0] == 0:
            return res[1]
        elif res[1] == 1:
            await self.bot.say(
                'No polls active in this channel. Please specify a pollid.')
            return None
        else:
            await self.bot.say(
                'More than one poll active in this channel. Please specify a pollid.'
            )
            return None

    def guessPollid(self, ctx):
        res = self.base.query('activemessages', ('pollid', ),
                              (('chanid', int(ctx.message.channel.id)), ))
        if len(res) == 0:
            return (1, None)  # No polls active in this channel
        elif len(res) == 1:
            return (0, res[0][0])
        else:
            return (2, None)  # More than one poll active in the channel

    async def render(self, framework, existingMess=None):
        res = framework.get()
        s = 'Poll ID ' + str(res['pollid']) + '\n'
        s = s + '**' + res['name'] + '**\n' + res['description'] + '\n\n'
        opened = False
        final = False

        if res['openTime'] > datetime.now():
            # Poll is not open yet
            s = s + 'This poll is **not open yet**! It will open on ' + res['openTime'].strftime('%A, %d %B %Y at %H:%M:%S')\
                  + ' and close on ' + res['closeTime'].strftime('%A, %d %B %Y at %H:%M:%S') + '.'
        elif res['closeTime'] < datetime.now():
            # Poll has already closed
            opened = True
            final = True
            s = s + 'This poll is **closed**! It opened on ' + res['openTime'].strftime('%A, %d %B %Y at %H:%M:%S')\
                  + ' and closed on ' + res['closeTime'].strftime('%A, %d %B %Y at %H:%M:%S') + '.'
        else:
            # Poll is open
            opened = True
            s = s + 'This poll is **open**! It has been open since ' + res['openTime'].strftime('%A, %d %B %Y at %H:%M:%S')\
                  + ' and will close on ' + res['closeTime'].strftime('%A, %d %B %Y at %H:%M:%S') + '.'

        # Render options
        s = s + '\n\n**Accepted responses**'
        for dex, option in enumerate(res['options']['short']):
            s = s + '\n\n**' + option + '**'
            emoj = None
            if res['options']['emoji'][dex]:
                emoj = self.typeEmoji(res['options']['emoji'][dex])
                s = s + ' (' + emoj + ')'
            s = s + ': ' + res['options']['long'][
                dex] + '\n' + self.genCmdString(option, emoj)

        if opened:
            s = s + '\n\n**'
            if final:
                s = s + 'Final '
            else:
                s = s + 'Current '
            s = s + 'Vote Totals**\n'
            # Render votes
            votes = framework.getVoteTotals()
            s = s + '```\n'
            scale = self.getScale(votes)
            scaleFactor = scale[0]
            padLength = scale[1]
            for key, vote in votes.items():
                s = s + self.pad(key, padLength) + ' ' + str(vote) + '|'
                if scaleFactor > 0:
                    for i in range(0, int(vote * scaleFactor)):
                        s = s + '#'
                s = s + '\n'

            s = s + '```'

            if existingMess:
                await self.bot.edit_message(existingMess, s)
            else:
                existingMess = await self.bot.say(s)

            for em in res['options']['emoji']:
                if em:
                    await self.bot.add_reaction(existingMess, em)

            return existingMess

    def pad(self, toPad, padLength):
        while len(toPad) < padLength:
            toPad = toPad + ' '

        return toPad

    def getScale(self, votes):
        maxVote = 0
        longestShortString = 0
        scaleFactor = 1
        for vote in votes.values():
            if vote > maxVote:
                maxVote = vote
        voteDigits = 0
        if maxVote > 0:
            voteDigits = int(math.log10(maxVote)) + 1

        for shortString in votes.keys():
            if len(shortString) > longestShortString:
                longestShortString = len(shortString)

        while maxVote * scaleFactor + voteDigits + longestShortString + 2 > 80:
            scaleFactor = scaleFactor - 0.1

        return (scaleFactor, longestShortString)

    def genCmdString(self, option, emoji):
        s = '(run ' + rc.PREF + 'poll vote ' + option
        if emoji:
            s = s + ' or click the reaction button below'
        s = s + ' to vote for this option)'
        return s

    def typeEmoji(self, emoji):
        emojiRx = re.compile('(:.+:)[0-9]+')
        res = emojiRx.match(emoji)
        if res:
            return res.group(1)
        else:
            return emoji
예제 #5
0
 def __init__(self, cmdCtx, bot):
     self.base = CabbageBase()
     self.cmdCtx = cmdCtx
     self.bot = bot
     random.seed()
예제 #6
0
class Phrasebook:
    def __init__(self, cmdCtx, bot):
        self.base = CabbageBase()
        self.cmdCtx = cmdCtx
        self.bot = bot
        random.seed()

    def phrasebookScan(self, mod, ctx):
        ''' Scan the phrasebook for the given context and return all results '''
        scRes = self.base.query2Filter('phrasebook', 'module', mod, 'context',
                                       ctx)
        phrases = []
        for res in scRes:
            phrases.append(res[2])

        return phrases

    def pickPhraseRaw(self, mod, ctx):
        ''' Randomly select a phrase from the given context; do not perform
		    substitution '''
        return random.choice(self.phrasebookScan(mod, ctx))

    def pickPhrase(self, mod, ctx, *customSubs):
        ''' Randomly select a phrase from the given context, and perform %
		    substitutions '''
        return self.doSubstitutions(self.pickPhraseRaw(mod, ctx), customSubs)

    def doSubstitutions(self, subs, *cSubs):
        ''' Performs % subsitutions on the given string '''
        modSt = ''
        isEscaped = False
        for c, actual in enumerate(subs):
            if subs[c] == '%':
                isEscaped = True
            elif not isEscaped:
                modSt += (subs[c])
            else:
                isEscaped = False
                if subs[c] == '%':
                    modSt += ('%')
                elif subs[c] == 'u':
                    modSt += (self.cmdCtx.message.author.name)
                elif subs[c] == 'U':
                    modSt += (self.cmdCtx.message.author.mention)
                elif subs[c] == 'm':
                    modSt += (self.bot.user.name)
                elif subs[c] == 'E':
                    modSt += ('@everyone')
                elif subs[c] == 't':
                    modSt += (datetime.now().strftime('%H:%M:%S'))
                elif subs[c] == 'T':
                    modSt += (
                        datetime.now().strftime('%H:%M:%S on %A, %d %B, %Y'))
                elif subs[c] == 'i':
                    modSt += (rc.PREF)
                else:
                    try:
                        intSub = int(subs[c])
                    except ValueError:
                        pass
                    else:
                        if intSub == 0:
                            intSub = 10
                        if len(cSubs) > 0 and intSub <= len(cSubs[0]):
                            modSt += str(cSubs[0][intSub - 1])
        return modSt
예제 #7
0
 def __init__(self):
     self.base = CabbageBase()
예제 #8
0
class FlagFramework:
    ''' Functions for managing simple flags in the SQL database
	  ' Note that flags are not unique -- that is, it is possible to have
		' multiple flags with the same name but different values.
		'''
    def __init__(self):
        self.base = CabbageBase()
        self.tables = [
            'baseflags', 'boolflags', 'textflags', 'intflags', 'userflags'
        ]
        self.ttypes = [None, bool, str, int, int]

    def fset(self, flag, module, server=0):
        ''' Sets a flag without a value '''
        cmd = 'INSERT INTO baseFlags (time, name, module, server) VALUES (%s,%s,%s,%s)'
        cur = self.base.getCursor()
        cur.execute(cmd, (datetime.now(), flag, module, int(server)))
        self.base.commit()
        cur.close()

    def bset(self, flag, module, server=0, value=True):
        ''' Sets a flag with a boolean value '''
        cmd = 'INSERT INTO boolFlags (time, name, module, flag, server) VALUES (%s,%s,%s,%s,%s)'
        cur = self.base.getCursor()
        cur.execute(cmd, (datetime.now(), flag, module, value, int(server)))
        self.base.commit()
        cur.close()

    def tset(self, flag, module, server=0, value=''):
        ''' Sets a flag with a plaintext value '''
        cmd = 'INSERT INTO textFlags (time, name, module, flag, server) VALUES (%s,%s,%s,%s,%s)'
        cur = self.base.getCursor()
        cur.execute(cmd, (datetime.now(), flag, module, value, int(server)))
        self.base.commit()
        cur.close()

    def iset(self, flag, module, server=0, value=0):
        ''' Sets a flag with an integer value '''
        cmd = 'INSERT INTO intFlags (time, name, module, flag, server) VALUES (%s,%s,%s,%s,%s)'
        cur = self.base.getCursor()
        cur.execute(cmd, (datetime.now(), flag, module, value, int(server)))
        self.base.commit()
        cur.close()

    def uset(self, flag, module, server, value):
        ''' Sets a flag with a discord user value '''
        cmd = 'INSERT INTO userFlags (time, name, module, flag, server) VALUES (%s,%s,%s,%s,%s)'
        cur = self.base.getCursor()
        cur.execute(cmd, (datetime.now(), flag, module, value, int(server)))
        self.base.commit()
        cur.close()

    def hasFlag(self, name, module, server=0):
        ''' Checks if the given flag exists, regardless of type or value '''
        for table in self.tables:
            if self.base.isPresentIn('name', name, table,
                                     (('module', module),
                                      ('server', int(server)))):
                return True

        return False

    def getFlag(self, name, module, server=0):
        ''' Gets all present values for the given flag '''
        flags = []
        for table in self.tables:
            if table == 'baseflags':
                query = self.base.query(table,
                                        ('time', 'name', 'module', 'server'),
                                        (('name', name), ('module', module),
                                         ('server', int(server))))
                if query and len(query) > 0:
                    for que in query:
                        flags.append({
                            'timestamp': que[0],
                            'name': que[1],
                            'module': que[2],
                            'flag': None,
                            'server': que[3]
                        })
            else:
                query = self.base.query(
                    table, ('time', 'name', 'module', 'flag', 'server'),
                    (('name', name), ('module', module),
                     ('server', int(server))))
                if query and len(query) > 0:
                    for que in query:
                        flags.append({
                            'timestamp': que[0],
                            'name': que[1],
                            'module': que[2],
                            'flag': que[3],
                            'server': que[4]
                        })
        if len(flags) > 0:
            return flags
        else:
            return None

    def delFlag(self, name, module, server=0, value=None):
        ''' Removes a flag from the database.'''
        for dex, table in enumerate(self.tables):
            if value and type(value) != self.ttypes[dex]:
                ''' This table can't possibly have the value. Skip it.'''
                continue
            cmd = 'DELETE FROM {} WHERE name = %s AND module = %s AND server = %s'
            if value and table != 'baseflags':
                cmd = cmd + ' AND flag = %s'
            cmd = cmd + ';'

            cur = self.base.getCursor()
            print('Removing flags with value ' + str(value) +
                  ' from server id ' + str(server))
            if value and table != 'baseflags':
                cur.execute(
                    sql.SQL(cmd).format(sql.Identifier(table)),
                    (name, module, server, value))
            else:
                cur.execute(
                    sql.SQL(cmd).format(sql.Identifier(table)),
                    (name, module, server))

        self.base.commit()
        cur.close()
예제 #9
0
 def __init__(self):
     self.base = CabbageBase()
     self.tables = [
         'baseflags', 'boolflags', 'textflags', 'intflags', 'userflags'
     ]
     self.ttypes = [None, bool, str, int, int]
예제 #10
0
class PollFramework:
    ''' Backend tasks for polls '''
    def __init__(self,
                 creator,
                 server,
                 name=None,
                 description=None,
                 openTime=None,
                 closeTime=None,
                 absoluteThreshold=None,
                 percentThreshold=None,
                 percentThresholdMinimum=None,
                 thresholdTime=None,
                 keepUpdated=True,
                 pollid=None):
        self.log = Logger()
        self.base = CabbageBase()
        self.creator = creator
        self.server = server
        self.name = name
        self.description = description
        self.openTime = openTime
        self.closeTime = closeTime
        self.absoluteThreshold = absoluteThreshold
        self.percentThreshold = percentThreshold
        self.percentThresholdMinimum = percentThresholdMinimum
        self.thresholdTime = thresholdTime
        self.options = {'short': [], 'long': [], 'emoji': []}
        self.keepUpdated = keepUpdated
        if pollid:
            self.pollid = pollid
        else:
            self.genPollid()
        self.update()

    @classmethod
    def fromSQL(cls, queryRow):
        log = Logger()
        log.log('Building pollid ' + str(queryRow[0]) + ' from SQL row',
                'poll', 8)
        working = cls(queryRow[1],
                      queryRow[2],
                      queryRow[3],
                      queryRow[4],
                      queryRow[5],
                      queryRow[6],
                      queryRow[7],
                      queryRow[8],
                      queryRow[9],
                      queryRow[10],
                      keepUpdated=False,
                      pollid=queryRow[0])
        opts = []
        for i in range(0, len(queryRow[11])):
            working.addOption(queryRow[11][i], queryRow[12][i],
                              queryRow[13][i])
        working.setUpdate(True)
        print('Built pollid ' + str(queryRow[0]))
        return working

    def genPollid(self):
        #while True:
        propPollid = random.randint(-1 * sys.maxsize, sys.maxsize)
        # This checks for the infinitesimal (1/(2*sys.maxsize+1) ~= 0) chance
        # of an id conflict. Uncomment if it ever becomes a problem. It shouldn't.
        #	if not self.base.isPresentIn('pollid', propPollid, 'polls'):
        #		break
        self.pollid = propPollid
        self.update()

    def setName(self, name):
        self.name = name
        self.update()

    def setDescription(self, desc):
        self.description = desc
        self.update()

    def setOpenTime(self, openTime):
        self.openTime = openTime
        self.update()

    def setCloseTime(self, closeTime):
        self.closeTime = closeTime
        self.update()

    def setAbsoluteThreshold(self, absThreshold):
        self.absoluteThreshold = absThreshold
        self.update()

    def setPercentThreshold(self, percThreshold):
        self.percentThreshold = percThreshold
        self.update()

    def setPercentThresholdMinimum(self, percThresholdMin):
        self.percentThresholdMinimum = percThresholdMin
        self.update()

    def setThresholdTime(self, threshTime):
        self.thresholdTime = threshTime
        self.update()

    def setUpdate(self, keepUp):
        self.keepUpdated = keepUp

    def addOption(self, shortOption, longOption, emojiOption=None):
        self.options['short'].append(shortOption)
        self.options['long'].append(longOption)
        if emojiOption:
            if re.match('<:.+:[0-9]+>', emojiOption):
                self.options['emoji'].append(emojiOption[-1:1])
            else:
                self.options['emoji'].append(emojiOption[0])
        else:
            self.options['emoji'].append(None)
        self.update()

    def addTrackingMessage(self, messid, chanid):
        ''' Add a message to the list of tracked messages '''
        res = self.base.query('activemessages',
                              filters=(('messid', int(messid)), ('chanid',
                                                                 int(chanid))))
        if res and len(res) > 0:
            # This message is already registered
            return False
        cmd = 'INSERT INTO activemessages (messid, chanid, pollid) VALUES (%s,%s,%s)'
        cur = self.base.getCursor()
        cur.execute(cmd, (int(messid), int(chanid), self.pollid))
        self.base.commit()
        cur.close()
        return True

    def delTrackingMessage(self, messid, chanid):
        ''' Remove a message from the list of tracked messages '''
        res = self.base.query('activemessages',
                              filters=(('messid', int(messid)), ('chanid',
                                                                 int(chanid))))
        if not res or len(res) == 0:
            # Message not found
            return False
        cmd = 'DELETE FROM ONLY activemessages WHERE messid = %s AND chanid = %s'
        cur = self.base.getCursor()
        cur.execute(cmd, (messid, chanid))
        self.base.commit()
        cur.close()
        return True

    def getTrackingMessages(self):
        ''' Return all messages that are supposed to be updated '''
        res = self.base.query('activemessages', ('messid', 'chanid', 'pollid'))
        processed = []
        for result in res:
            # Convert to dict for convenience
            processed.append({
                'messid': result[0],
                'chanid': result[1],
                'pollid': result[2]
            })
        return processed

    def get(self):
        ''' Return a dictionary containing all of the relevant poll parameters
		    (i.e. everything except votes and tracking messages)
		'''
        return {
            'creator': self.creator,
            'server': self.server,
            'name': self.name,
            'description': self.description,
            'pollid': self.pollid,
            'openTime': self.openTime,
            'closeTime': self.closeTime,
            'absoluteThreshold': self.absoluteThreshold,
            'percentThreshold': self.percentThreshold,
            'percentThresholdMinimum': self.percentThresholdMinimum,
            'thresholdTime': self.thresholdTime,
            'options': self.options
        }

    def vote(self, user, shortOption):
        ''' Registers a vote from a user by short option '''
        res = self.base.query('polls',
                              ('shortoptions', 'opentime', 'closetime'),
                              (('pollid', self.pollid), ))

        curTime = datetime.now()
        if curTime - res[0][1] < timedelta(
                seconds=0) or curTime - res[0][2] > timedelta(seconds=0):
            return 2  # Poll is closed

        cmd = 'DELETE FROM ONLY votes WHERE voterid = %s AND pollid = %s;'
        cur = self.base.getCursor()
        cur.execute(cmd, (user, self.pollid))
        if shortOption not in res[0][0]:
            self.base.rollback()
            cur.close()
            return 1  # Invalid option

        cmd = 'INSERT INTO votes (voterid, pollid, voteTime, vote) VALUES (%s,%s,%s,%s)'
        cur.execute(cmd, (user, self.pollid, datetime.now(), shortOption))
        self.base.commit()
        cur.close()
        return 0  # OK

    def voteEmoji(self, user, emojiOption):
        ''' Registers a votefrom a user by emoji option '''
        cmd = 'DELETE FROM ONLY votes WHERE voterid = %s AND pollid = %s;'
        cur = self.base.getCursor()
        cur.execute(cmd, (user, self.pollid))
        res = self.base.query('polls', ('shortoptions', 'emojioptions'),
                              (('pollid', self.pollid), ))
        shortEq = None
        for dex, shortOpt in enumerate(res[0][0]):
            if res[0][1][dex] == emojiOption:
                shortEq = shortOpt

        if not shortEq:
            # User voted with invalid emoji. Bad user.
            self.base.rollback()
            cur.close()
            return 1

        return self.vote(user, shortEq)

    def getVoteDetails(self):
        ''' Retrieves all votes from the SQL database '''
        return self.base.query('votes',
                               ('voterid', 'pollid', 'votetime', 'vote'),
                               (('pollid', self.pollid), ))

    def getVoteTotals(self):
        ''' Retrieves the total number of votes for each option '''
        counts = {}
        cur = self.base.getCursor()
        for option in self.options['short']:
            cur.execute('SELECT * FROM votes WHERE pollid = %s AND vote = %s',
                        (self.pollid, option))
            counts[option] = cur.rowcount
        cur.close()
        return counts

    def update(self):
        ''' Update the SQL database to reflect changes to the object '''
        if not self.keepUpdated:
            return  # Update only functions if the object has keepUpdated set true
        self.log.log(
            'Poll ID ' + str(self.pollid) + ' (' + self.name + ') updated.',
            'poll', 7)
        cur = self.base.getCursor()
        table = 'polls'
        execString = '(creatorid, serverid, name, description, pollid, openTime, closeTime, absoluteThreshold, percentThreshold, percentThresholdMinimum, thresholdTime, shortOptions, longOptions, emojiOptions)'
        valString = '(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'
        caveat = 'WHERE pollid=' + str(self.pollid)
        constructed = 'UPDATE ONLY ' + table + ' SET ' + execString + ' = ' + valString + caveat + ';'
        print(str(cur.mogrify( \
         constructed, \
          (self.creator, \
          self.server, \
          self.name, \
          self.description, \
          self.pollid, \
          self.openTime, \
          self.closeTime, \
          self.absoluteThreshold, \
          self.percentThreshold, \
          self.percentThresholdMinimum, \
          self.thresholdTime, \
          self.options['short'], \
          self.options['long'], \
          self.options['emoji'] \
          )\
         )))
        cur.execute( \
         constructed, \
          (self.creator, \
          self.server, \
          self.name, \
          self.description, \
          self.pollid, \
          self.openTime, \
          self.closeTime, \
          self.absoluteThreshold, \
          self.percentThreshold, \
          self.percentThresholdMinimum, \
          self.thresholdTime, \
          self.options['short'], \
          self.options['long'], \
          self.options['emoji'] \
          )\
         )

        if cur.statusmessage == 'UPDATE 0':
            # This is a new poll; insert it into the table
            constructed = 'INSERT INTO ' + table + execString + ' VALUES ' + valString + ';'
            cur.execute( \
             constructed, \
              (self.creator, \
              self.server, \
              self.name, \
              self.description, \
              self.pollid, \
              self.openTime, \
              self.closeTime, \
              self.absoluteThreshold, \
              self.percentThreshold, \
              self.percentThresholdMinimum, \
              self.thresholdTime, \
              self.options['short'], \
              self.options['long'], \
              self.options['emoji'] \
              )\
             )
            ms = 'Created new poll with pollid ' + str(self.pollid)
            rc.pinfo(ms)
            self.log.log(ms, 'poll', 6)
            self.base.commit()
        elif cur.statusmessage != 'UPDATE 1':
            errm = 'Rolling back UPDATE command for Poll object due to abnormal response -- check for multiple polls with the same pollid: ' + str(
                self.pollid) + ' (returned message: ' + str(
                    cur.statusmessage) + ')'
            rc.perr(errm)
            self.log.log(errm, 'poll', 2)
            self.base.rollback()
        else:
            # Normal response; safe to commit
            self.base.commit()

        # Either way, release the database hold
        cur.close()