<commit_before>import argparse
import asyncio
import logging
import operator
from datetime import datetime, timedelta

import discord
from sqlalchemy import text

from bot.bot import command
from utils.utilities import (get_emote_name_id, parse_time, datetime2sql,
                             get_avatar)

logger = logging.getLogger('debug')

class Poll:
    def __init__(self, bot, message, channel, title, expires_at=None,
                 strict=False, emotes=None, no_duplicate_votes=False,
                 multiple_votes=False, max_winners=1):
        """
        Args:
            message: either `class`: discord.Message or int
            others explained in VoteManager
        """
        self._bot = bot
        self.message = message
        self.channel = str(channel)
        self.title = title
        self.expires_at = expires_at
        self.strict = strict
        self._emotes = emotes or []
        self.ignore_on_dupe = no_duplicate_votes
        self.multiple_votes = multiple_votes
        self.max_winners = max_winners
        self._task = None
        self._stopper = asyncio.Event()

    @property
    def bot(self):
        return self._bot

    def add_emote(self, emote_id):
        # Used when recreating the poll
        self._emotes.append(emote_id)

    def start(self):
        self._stopper.clear()
        self._task = self.bot.loop.create_task(self._wait())

    def stop(self):
        if self._task:
            try:
                self._task.cancel()
            except:
                pass

    def count_now(self):
        self._stopper.set()

    async def _wait(self):
        try:
            time_ = self.expires_at - datetime.utcnow()
            time_ = time_.total_seconds()
            if time_ > 0:
                await asyncio.wait_for(self._stopper.wait(), timeout=time_, loop=self.bot.loop)
        except asyncio.TimeoutError:
            pass
        except asyncio.CancelledError:
            return

        await self.count_votes()

    async def count_votes(self):
        if isinstance(self.message, discord.Message):
            self.message = self.message.id

        session = self.bot.get_session
        self.message = str(self.message)
        try:
            chn = self.bot.get_channel(self.channel)
            msg = await self.bot.get_message(chn, self.message)
        except:
            logger.exception('Failed to end poll')
            channel = self.bot.get_channel(self.channel)
            sql = 'DELETE FROM `polls` WHERE `message`= %s' % self.message
            try:
                session.execute(sql)
                session.commit()
            except:
                logger.exception('Could not delete poll')
            return await self.bot.send_message(channel, 'Failed to end poll.\nReason: Could not get the poll message')

        votes = {}
        for reaction in msg.reactions:
            if self.strict:
                # Optimization LUL
                if isinstance(reaction.emoji, str):
                    if len(reaction.emoji) > 1:
                        continue

                id = ord(reaction.emoji) if isinstance(reaction.emoji, str) else reaction.emoji.id
                if id not in self._emotes:
                    continue

            users = await self.bot.get_reaction_users(reaction, limit=reaction.count)

            for user in users:
                if user.bot:
                    continue

                if self.ignore_on_dupe and user.id in votes:
                    votes[user.id] = None
                elif not self.multiple_votes:
                    votes[user.id] = [str(reaction.emoji)]
                else:
                    if user.id in votes:
                        votes[user.id] += (str(reaction.emoji), )
                    else:
                        votes[user.id] = [str(reaction.emoji)]

        scores = {}
        for u, reacts in votes.items():
            if reacts is None:
                continue

            for r in reacts:
                score = scores.get(r, 0)
                scores[r] = score + 1

        scores = sorted(scores.items(), key=operator.itemgetter(1),  reverse=True)
        if scores:
            current_winners = []
            winners = 0
            current_score = -1
            end = '\nWinner(s) are '
            for emote, score in scores:
                if score > current_score:
                    current_score = score
                    current_winners.append(emote)
                elif score == current_score:
                    current_winners.append(emote)
                elif self.max_winners > winners and current_score >= score:
                    if current_score > score:
                        end += '{} with the score of {} '.format(' '.join(current_winners), current_score)
                        current_winners = []
                        current_score = score

                    current_winners.append(emote)
                else:
                    break

                winners += 1

            end += '{} with the score of {}'.format(' '.join(current_winners), current_score)
        else:
            end = ' with no winners'

        s = 'Poll ``{}`` ended{}'.format(self.title, end)
        await self.bot.send_message(chn, s)

        sql = 'DELETE FROM `polls` WHERE `message`= %s' % self.message
        try:
            session.execute(sql)
            session.commit()
        except:
            logger.exception('Could not delete poll')
            await self.bot.send_message(chn, 'Could not delete poll from database. The result might be recalculated')

class VoteManager:
    def __init__(self, bot):
        self.bot = bot
        self.session = self.bot.get_session
        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('-header', nargs='+')
        self.parser.add_argument('-time', default='60s', nargs='+')
        self.parser.add_argument('-emotes', default=None, nargs='+')
        self.parser.add_argument('-description', default=None, nargs='+')
        self.parser.add_argument('-max_winners', default=1, type=int)
        self.parser.add_argument('-strict', action='store_true')
        self.parser.add_argument('-no_duplicate_votes', action='store_true')
        self.parser.add_argument('-allow_multiple_entries', action='store_true')

    @command(pass_context=True, owner_only=True, aliases=['vote'])
    async def poll(self, ctx, *, message):
        """
        Create a poll. See help poll for arguments
        Creates a poll that expires by default in 60 seconds
        Examples of use: -poll title -d description -e <:gappyGoodShit:326946656023085056> 👌 -a
        available arguments
        `-d` `-description` Description for the poll
        `-t` `-time` Time after which the poll is expired. Maximum time is 1 week
        `-e` `-emotes` Optional emotes that are automatically added to the poll

        These options require no arguments. Default values that are used when they aren't specified are marked in square brackets []
        `-m` `-max_winners` [1] Maximum amount of winners. It might be more in case of a draw
        `-s` `-strict` [false] Only count emotes specified in the -emotes argument
        `-n` `-no_duplicate_votes` [false] Ignores users who react to more than one emote
        `-a` `-allow_multiple_entries` [false] Count all reactions from the user. Even if that user reacted with multiple emotes.
        """
        # TODO Add permission check

        # Add -header if it's not present so argparser can recognise the argument
        message = '-header ' + message if not message.startswith('-h') else message
        try:
            parsed = self.parser.parse_args(message.split(' '))
        except:
            return await self.bot.say('Failed to parse arguments')

        if parsed.strict and not parsed.emotes:
            return await self.bot.say('Cannot set strict mode without specifying any emotes')

        if parsed.no_duplicate_votes and parsed.allow_multiple_entries:
            return await self.bot.say('Cannot have -n and -a specified at the same time. That would be dumb')

        if parsed.max_winners < 1:
            return await self.bot.say('Max winners needs to be an integer bigger than 0')

        parsed.max_winners = min(parsed.max_winners, 20)

        title = ' '.join(parsed.header)
        expires_in = parse_time(' '.join(parsed.time))
        if expires_in.total_seconds() == 0:
            await self.bot.say('No time specified or time given is 0 seconds. Using default value of 60s')
            expires_in = timedelta(seconds=60)
        if expires_in.days > 7:
            return await self.bot.say('Maximum time is 7 days')

        now = datetime.utcnow()
        expired_date = now + expires_in
        sql_date = datetime2sql(expired_date)
        parsed.time = sql_date

        emotes = []
        failed = []
        if parsed.emotes:
            for emote in parsed.emotes:
                if not emote.strip():
                    continue

                name = get_emote_name_id(emote)
                if name is None:
                    # TODO Better check for flag emotes
                    if len(emote) > 1:
                        continue
                    emotes.append(emote)
                else:
                    emotes.append(name)

        if parsed.description:
            description = ' '.join(parsed.description)
        else:
            description = discord.Embed.Empty

        embed = discord.Embed(title=title, description=description, timestamp=expired_date)
        if parsed.time:
            embed.add_field(name='Valid for',
                            value='%s' % str(expires_in))
        embed.set_footer(text='Expires at', icon_url=get_avatar(ctx.message.author))

        options = ''
        if parsed.strict:
            options += 'Strict mode on. Only specified emotes are counted\n'

        if parsed.no_duplicate_votes:
            options += 'Voting for more than one valid option will invalidate your vote\n'

        if parsed.allow_multiple_entries:
            options += 'All all valid votes are counted from a user\n'

        if parsed.max_winners > 1:
            options += 'Max amount of winners %s (might be more in case of a tie)' % parsed.max_winners

        if options:
            embed.add_field(name='Modifiers', value=options)

        msg = await self.bot.send_message(ctx.message.channel, embed=embed)

        # add reactions to message
        for emote in emotes:
            try:
                emote = '{}:{}'.format(*emote) if isinstance(emote, tuple) else emote
                await self.bot.add_reaction(msg, emote)
            except:
                failed.append(emote)
        if failed:
            await self.bot.say('Failed to get emotes `{}`'.format('` `'.join(failed)),
                               delete_after=60)

        sql = 'INSERT INTO `polls` (`server`, `title`, `strict`, `message`, `channel`, `expires_in`, `ignore_on_dupe`, `multiple_votes`, `max_winners`) ' \
              'VALUES (:server, :title, :strict, :message, :channel, :expires_in, :ignore_on_dupe, :multiple_votes, :max_winners)'
        d = {'server': ctx.message.server.id, 'title': title,
             'strict': parsed.strict, 'message': msg.id, 'channel': ctx.message.channel.id,
             'expires_in': parsed.time, 'ignore_on_dupe': parsed.no_duplicate_votes,
             'multiple_votes': parsed.allow_multiple_entries, 'max_winners': parsed.max_winners}
        try:
            self.session.execute(text(sql), params=d)

            emotes_list = []
            if emotes:
                sql = 'INSERT INTO `emotes` (`name`, `emote`, `server`) VALUES '
                values = []
                # We add all successfully parsed emotes even if the bot failed to
                # add them so strict mode will count them in too
                for emote in emotes:
                    if not isinstance(emote, tuple):
                        name, id = emote, ord(emote)
                        emotes_list.append(id)
                        server = 'NULL'
                    else:
                        name, id = emote
                        emotes_list.append(id)
                        server = ctx.message.server.id

                    values.append('("%s", %s, %s)' % (name, id, server))

                # If emote is already in the table update its name
                sql += ', '.join(values) + ' ON DUPLICATE KEY UPDATE name=name'
                self.session.execute(text(sql))

                sql = 'INSERT IGNORE INTO `pollEmotes` (`poll_id`, `emote_id`) VALUES '
                values = []
                for id in emotes_list:
                    values.append('(%s, %s)' % (msg.id, id))

                sql += ', '.join(values)
                self.session.execute(text(sql))

            self.session.commit()
        except:
            logger.exception('Failed sql query')
            return await self.bot.say('Failed to save poll. Exception has been logged')

        poll = Poll(self.bot, msg.id, msg.channel.id, title, expires_at=expired_date, strict=parsed.strict,
                    emotes=emotes_list, no_duplicate_votes=parsed.no_duplicate_votes,
                    multiple_votes=parsed.allow_multiple_entries, max_winners=parsed.max_winners)
        poll.start()

def setup(bot):
    bot.add_cog(VoteManager(bot))<commit_msg>Add role granting thingy and small fixes and improvements<commit_after>import argparse
import asyncio
import logging
import operator
from datetime import datetime, timedelta

import discord
from sqlalchemy import text

from bot.bot import command
from utils.utilities import (get_emote_name_id, parse_time, datetime2sql,
                             get_avatar)

logger = logging.getLogger('debug')

class Poll:
    def __init__(self, bot, message, channel, title, expires_at=None,
                 strict=False, emotes=None, no_duplicate_votes=False,
                 multiple_votes=False, max_winners=1):
        """
        Args:
            message: either `class`: discord.Message or int
            others explained in VoteManager
        """
        self._bot = bot
        self.message = message
        self.channel = str(channel)
        self.title = title
        self.expires_at = expires_at
        self.strict = strict
        self._emotes = emotes or []
        self.ignore_on_dupe = no_duplicate_votes
        self.multiple_votes = multiple_votes
        self.max_winners = max_winners
        self._task = None
        self._stopper = asyncio.Event()

    @property
    def bot(self):
        return self._bot

    def add_emote(self, emote_id):
        # Used when recreating the poll
        self._emotes.append(emote_id)

    def start(self):
        self._stopper.clear()
        self._task = self.bot.loop.create_task(self._wait())

    def stop(self):
        if self._task:
            try:
                self._task.cancel()
            except:
                pass

    def count_now(self):
        self._stopper.set()

    async def _wait(self):
        try:
            time_ = self.expires_at - datetime.utcnow()
            time_ = time_.total_seconds()
            if time_ > 0:
                await asyncio.wait_for(self._stopper.wait(), timeout=time_, loop=self.bot.loop)
        except asyncio.TimeoutError:
            pass
        except asyncio.CancelledError:
            return

        await self.count_votes()

    async def count_votes(self):
        if isinstance(self.message, discord.Message):
            self.message = self.message.id

        session = self.bot.get_session
        self.message = str(self.message)
        try:
            chn = self.bot.get_channel(self.channel)
            msg = await self.bot.get_message(chn, self.message)
        except:
            logger.exception('Failed to end poll')
            channel = self.bot.get_channel(self.channel)
            sql = 'DELETE FROM `polls` WHERE `message`= %s' % self.message
            try:
                session.execute(sql)
                session.commit()
            except:
                logger.exception('Could not delete poll')
            return await self.bot.send_message(channel, 'Failed to end poll.\nReason: Could not get the poll message')

        votes = {}
        for reaction in msg.reactions:
            if self.strict:
                # Optimization LUL
                if isinstance(reaction.emoji, str):
                    if len(reaction.emoji) > 1:
                        continue

                id = ord(reaction.emoji) if isinstance(reaction.emoji, str) else reaction.emoji.id
                if id not in self._emotes:
                    continue

            users = await self.bot.get_reaction_users(reaction, limit=reaction.count)

            for user in users:
                if user.bot:
                    continue

                if self.ignore_on_dupe and user.id in votes:
                    votes[user.id] = None
                elif not self.multiple_votes:
                    votes[user.id] = [str(reaction.emoji)]
                else:
                    if user.id in votes:
                        votes[user.id] += (str(reaction.emoji), )
                    else:
                        votes[user.id] = [str(reaction.emoji)]

        scores = {}
        for u, reacts in votes.items():
            if reacts is None:
                continue

            for r in reacts:
                score = scores.get(r, 0)
                scores[r] = score + 1

        scores = sorted(scores.items(), key=operator.itemgetter(1),  reverse=True)
        if scores:
            current_winners = []
            winners = 0
            current_score = -1
            end = '\nWinner(s) are '
            for emote, score in scores:
                if score > current_score:
                    current_score = score
                    current_winners.append(emote)
                elif score == current_score:
                    current_winners.append(emote)
                elif self.max_winners > winners and current_score >= score:
                    if current_score > score:
                        end += '{} with the score of {} '.format(' '.join(current_winners), current_score)
                        current_winners = []
                        current_score = score

                    current_winners.append(emote)
                else:
                    break

                winners += 1

            end += '{} with the score of {}'.format(' '.join(current_winners), current_score)
        else:
            end = ' with no winners'

        s = 'Poll ``{}`` ended{}'.format(self.title, end)
        await self.bot.send_message(chn, s)

        sql = 'DELETE FROM `polls` WHERE `message`= %s' % self.message
        try:
            session.execute(sql)
            session.commit()
        except:
            logger.exception('Could not delete poll')
            await self.bot.send_message(chn, 'Could not delete poll from database. The result might be recalculated')

class VoteManager:
    def __init__(self, bot):
        self.bot = bot
        self.session = self.bot.get_session
        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('-header', nargs='+')
        self.parser.add_argument('-time', default='60s', nargs='+')
        self.parser.add_argument('-emotes', default=None, nargs='+')
        self.parser.add_argument('-description', default=None, nargs='+')
        self.parser.add_argument('-max_winners', default=1, type=int)
        self.parser.add_argument('-strict', action='store_true')
        self.parser.add_argument('-no_duplicate_votes', action='store_true')
        self.parser.add_argument('-allow_multiple_entries', action='store_true')

    @command(pass_context=True, owner_only=True, aliases=['vote'])
    async def poll(self, ctx, *, message):
        """
        Create a poll. See help poll for arguments
        Creates a poll that expires by default in 60 seconds
        Examples of use: -poll title -d description -e <:gappyGoodShit:326946656023085056> 👌 -a
        available arguments
        `-d` `-description` Description for the poll
        `-t` `-time` Time after which the poll is expired. Maximum time is 1 week
        `-e` `-emotes` Optional emotes that are automatically added to the poll

        These options require no arguments. Default values that are used when they aren't specified are marked in square brackets []
        `-m` `-max_winners` [1] Maximum amount of winners. It might be more in case of a draw
        `-s` `-strict` [false] Only count emotes specified in the -emotes argument
        `-n` `-no_duplicate_votes` [false] Ignores users who react to more than one emote
        `-a` `-allow_multiple_entries` [false] Count all reactions from the user. Even if that user reacted with multiple emotes.
        """
        # TODO Add permission check

        # Add -header if it's not present so argparser can recognise the argument
        message = '-header ' + message if not message.startswith('-h') else message
        try:
            parsed = self.parser.parse_args(message.split(' '))
        except:
            return await self.bot.say('Failed to parse arguments')

        if parsed.strict and not parsed.emotes:
            return await self.bot.say('Cannot set strict mode without specifying any emotes')

        if parsed.no_duplicate_votes and parsed.allow_multiple_entries:
            return await self.bot.say('Cannot have -n and -a specified at the same time. That would be dumb')

        if parsed.max_winners < 1:
            return await self.bot.say('Max winners needs to be an integer bigger than 0')

        parsed.max_winners = min(parsed.max_winners, 20)

        title = ' '.join(parsed.header)
        expires_in = parse_time(' '.join(parsed.time))
        if expires_in.total_seconds() == 0:
            await self.bot.say('No time specified or time given is 0 seconds. Using default value of 60s')
            expires_in = timedelta(seconds=60)
        if expires_in.days > 7:
            return await self.bot.say('Maximum time is 7 days')

        now = datetime.utcnow()
        expired_date = now + expires_in
        sql_date = datetime2sql(expired_date)
        parsed.time = sql_date

        emotes = []
        failed = []
        if parsed.emotes:
            for emote in parsed.emotes:
                if not emote.strip():
                    continue

                name = get_emote_name_id(emote)
                if name is None:
                    # TODO Better check for flag emotes
                    if len(emote) > 1:
                        continue
                    emotes.append(emote)
                else:
                    emotes.append(name)

        if parsed.description:
            description = ' '.join(parsed.description)
        else:
            description = discord.Embed.Empty

        embed = discord.Embed(title=title, description=description, timestamp=expired_date)
        if parsed.time:
            embed.add_field(name='Valid for',
                            value='%s' % str(expires_in))
        embed.set_footer(text='Expires at', icon_url=get_avatar(ctx.message.author))

        options = ''
        if parsed.strict:
            options += 'Strict mode on. Only specified emotes are counted\n'

        if parsed.no_duplicate_votes:
            options += 'Voting for more than one valid option will invalidate your vote\n'
        elif not parsed.allow_multiple_entries:
            options += 'If user votes multiple times only 1 reaction is counted'

        if parsed.allow_multiple_entries:
            options += 'All all valid votes are counted from a user\n'

        if parsed.max_winners > 1:
            options += 'Max amount of winners %s (might be more in case of a tie)' % parsed.max_winners

        if options:
            embed.add_field(name='Modifiers', value=options)

        msg = await self.bot.send_message(ctx.message.channel, embed=embed)

        # add reactions to message
        for emote in emotes:
            try:
                emote = '{}:{}'.format(*emote) if isinstance(emote, tuple) else emote
                await self.bot.add_reaction(msg, emote)
            except:
                failed.append(emote)
        if failed:
            await self.bot.say('Failed to get emotes `{}`'.format('` `'.join(failed)),
                               delete_after=60)

        sql = 'INSERT INTO `polls` (`server`, `title`, `strict`, `message`, `channel`, `expires_in`, `ignore_on_dupe`, `multiple_votes`, `max_winners`) ' \
              'VALUES (:server, :title, :strict, :message, :channel, :expires_in, :ignore_on_dupe, :multiple_votes, :max_winners)'
        d = {'server': ctx.message.server.id, 'title': title,
             'strict': parsed.strict, 'message': msg.id, 'channel': ctx.message.channel.id,
             'expires_in': parsed.time, 'ignore_on_dupe': parsed.no_duplicate_votes,
             'multiple_votes': parsed.allow_multiple_entries, 'max_winners': parsed.max_winners}
        try:
            self.session.execute(text(sql), params=d)

            emotes_list = []
            if emotes:
                sql = 'INSERT INTO `emotes` (`name`, `emote`, `server`) VALUES '
                values = []
                # We add all successfully parsed emotes even if the bot failed to
                # add them so strict mode will count them in too
                for emote in emotes:
                    if not isinstance(emote, tuple):
                        name, id = emote, ord(emote)
                        emotes_list.append(id)
                        server = 'NULL'
                    else:
                        name, id = emote
                        emotes_list.append(id)
                        server = ctx.message.server.id

                    values.append('("%s", %s, %s)' % (name, id, server))

                # If emote is already in the table update its name
                sql += ', '.join(values) + ' ON DUPLICATE KEY UPDATE name=name'
                self.session.execute(text(sql))

                sql = 'INSERT IGNORE INTO `pollEmotes` (`poll_id`, `emote_id`) VALUES '
                values = []
                for id in emotes_list:
                    values.append('(%s, %s)' % (msg.id, id))

                sql += ', '.join(values)
                self.session.execute(text(sql))

            self.session.commit()
        except:
            logger.exception('Failed sql query')
            return await self.bot.say('Failed to save poll. Exception has been logged')

        poll = Poll(self.bot, msg.id, msg.channel.id, title, expires_at=expired_date, strict=parsed.strict,
                    emotes=emotes_list, no_duplicate_votes=parsed.no_duplicate_votes,
                    multiple_votes=parsed.allow_multiple_entries, max_winners=parsed.max_winners)
        poll.start()

def setup(bot):
    bot.add_cog(VoteManager(bot))<|endoftext|>


<commit_before>
import heapq
import itertools
from itertools import groupby
import operator
import socket
import struct
import time

__all__ = [
    'ipv4_int_to_str',
    'ipv4_str_to_int',
    'ipv4_int_to_bytes',
    'ipv4_bytes_to_int',
    'merge_ranges',
    'PeriodicCallback',
]

#
# IP address conversion utilities
#

IPV4_STRUCT = struct.Struct('>L')

def ipv4_int_to_str(n, _inet_ntoa=socket.inet_ntoa, _pack=IPV4_STRUCT.pack):
    """Convert an integer into an IPv4 address string.

    This function converts a (max 32 bit) integer into the common
    dot-decimal notation for IP addresses. Example: ``0x01020304``
    becomes *1.2.3.4*.
    """
    return _inet_ntoa(_pack(n))

def ipv4_str_to_int(s, _inet_aton=socket.inet_aton,
                    _unpack=IPV4_STRUCT.unpack):
    """Convert an IPv4 address string into an integer.

    This is the reverse of :py:func:`ipv4_int_to_str`.
    """
    return _unpack(_inet_aton(s))[0]

def ipv4_bytes_to_int(b, _unpack=IPV4_STRUCT.unpack):
    """Convert a 4 byte string into an integer"""
    return _unpack(b)[0]

def ipv4_int_to_bytes(n, _pack=IPV4_STRUCT.pack):
    """Convert an integer into 4 byte string"""
    return _pack(n)

#
# Range merging
#

EVENT_TYPE_BEGIN = 0
EVENT_TYPE_END = 1

def merge_ranges(*inputs):
    """
    Merge multiple ranges into a combined stream of ranges.

    This function combines all inputs by generating "change events" for
    each individual input, and merging these together in a single stream
    of changes. It then groups the change stream by position, and
    iterates over it, yielding a snapshot at each change point.
    """

    def generate_changes(it, input_id):
        for begin, end, data in it:
            assert begin <= end
            yield begin, EVENT_TYPE_BEGIN, input_id, data
            yield end + 1, EVENT_TYPE_END, input_id, None

    changes_generators = [
        generate_changes(input, input_id)
        for input_id, input in enumerate(inputs)
    ]
    all_changes = heapq.merge(*changes_generators)
    grouper = itertools.groupby(all_changes, operator.itemgetter(0))
    active = {}
    previous_position = None

    for position, changes in grouper:

        # Yield output range from the previous position up to the
        # current position, containing all currently valid values.
        if active:
            yield previous_position, position - 1, active.values()

        # Apply begin/end changes
        for _, event_type, input_id, data in changes:
            if event_type == EVENT_TYPE_BEGIN:
                assert input_id not in active
                active[input_id] = data
            elif event_type == EVENT_TYPE_END:
                del active[input_id]

        # Remember current position
        previous_position = position

    # After consuming all changes, all ranges must be closed.
    assert len(active) == 0

#
# Dict diffing and squashing
#

def dict_diff(d, base):
    """
    Calculate differences between a dict and a base dict.

    The return value is a tuple with two items: a `to_set` dictionary
    with all items in `d` that were either unset or changed, and
    a `to_delete` tuple with all removed keys.

    Note: this function does not recursively compare dicts; only the
    keys and values in the supplied dicts will be compared.

    See also dict_patch().
    """
    # The loop below is equivalent to
    #
    #   to_set = dict(d.viewitems() - base.viewitems())
    #
    # ...but the loop below is more performant for dicts with more than
    # a few keys.
    to_set = {}
    for k, v in d.items():
        if not k in base:
            to_set[k] = d[k]  # addition
        elif base[k] != v:
            to_set[k] = v  # mutation

    to_delete = tuple(base.keys() - d.keys())
    return to_set, to_delete

def dict_patch(d, to_set, to_delete):
    """
    Patches a dictionary using a changes dict and a list of deletions.

    This is the reverse of dict_diff().
    """
    d.update(to_set)
    for k in to_delete:
        del d[k]

def dict_diff_decremental(dicts):
    """Create a list of decremental diffs for a sorted list of dicts.

    The input should be sorted from oldest to latest. The output is in
    reverse older, i.e. from latest to oldest. This means that the
    latest version (`dicts[-1]`) is required as the starting point when
    patching.

    For `n` input dicts, `n - 1` reverse diffs will be returned.
    """
    return [
        dict_diff(dicts[i - 1], dicts[i])
        for i in range(len(dicts) - 1, 0, -1)
    ]

def squash_duplicate_dicts(
        dicts, ignored_key=None,
        _ig1=operator.itemgetter(1), _NOT_SET=object()):
    """Deduplicate a list of dicts by squashing adjacent identical dicts.

    This functions takes a list of dicts and returns only the first dict
    of each "run" of identical dicts, in the original order, i.e.
    `[d1, d1, d1, d2, d2, d3, d1]` results in `[d1, d2, d3, d1]`.

    The `ignored_key` arg specifies a key to ignore when comparing the
    dict (which uses a normal `d1 == d2` equality test).
    """
    # Step 1: preparation. Pop (and remember) the key to ignore, but
    # first copy the dicts (instances are "borrowed"), so that we can
    # safely mutate them.
    dicts = [d.copy() for d in dicts]
    transformed = [(d.pop(ignored_key, _NOT_SET), d) for d in dicts]

    # Step 2: deduplication. Group identical information, keeping only
    # the first occurrence of each unique dict. The implementation is
    # based on the unique_justseen() recipe from the itertools docs:
    # group by actual value and take only the first item of each
    # group.
    #
    # Note: the grouper is a generator (lazy), so explicitly turn the
    # result into a list, as the dicts will be modified inside the loop
    # below. Not doing so breaks the comparison inside the grouper.
    squashed = list(map(next, map(_ig1, groupby(transformed, _ig1))))

    # Step 3: transform to original format. Add back the ignored key to obtain
    # dicts in the original format.
    uniques = []
    _append = uniques.append
    for ignored_value, d in squashed:
        if ignored_value is not _NOT_SET:
            # Write back previously extracted ignored key/value (if any)
            d[ignored_key] = ignored_value
        _append(d)

    return uniques

#
# Progress logging
#

DEFAULT_CALLBACK_INTERVAL = 10

class PeriodicCallback(object):
    def __init__(self, cb, interval=DEFAULT_CALLBACK_INTERVAL):
        assert callable(cb)
        self._cb = cb
        self._interval = interval
        self._last_report = float('-inf')

    def tick(self, force_report=False):
        if force_report or time.time() - self._last_report > self._interval:
            self._cb()
            self._last_report = time.time()
<commit_msg>Update comment in dict_diff() for Python 3<commit_after>
import heapq
import itertools
from itertools import groupby
import operator
import socket
import struct
import time

__all__ = [
    'ipv4_int_to_str',
    'ipv4_str_to_int',
    'ipv4_int_to_bytes',
    'ipv4_bytes_to_int',
    'merge_ranges',
    'PeriodicCallback',
]

#
# IP address conversion utilities
#

IPV4_STRUCT = struct.Struct('>L')

def ipv4_int_to_str(n, _inet_ntoa=socket.inet_ntoa, _pack=IPV4_STRUCT.pack):
    """Convert an integer into an IPv4 address string.

    This function converts a (max 32 bit) integer into the common
    dot-decimal notation for IP addresses. Example: ``0x01020304``
    becomes *1.2.3.4*.
    """
    return _inet_ntoa(_pack(n))

def ipv4_str_to_int(s, _inet_aton=socket.inet_aton,
                    _unpack=IPV4_STRUCT.unpack):
    """Convert an IPv4 address string into an integer.

    This is the reverse of :py:func:`ipv4_int_to_str`.
    """
    return _unpack(_inet_aton(s))[0]

def ipv4_bytes_to_int(b, _unpack=IPV4_STRUCT.unpack):
    """Convert a 4 byte string into an integer"""
    return _unpack(b)[0]

def ipv4_int_to_bytes(n, _pack=IPV4_STRUCT.pack):
    """Convert an integer into 4 byte string"""
    return _pack(n)

#
# Range merging
#

EVENT_TYPE_BEGIN = 0
EVENT_TYPE_END = 1

def merge_ranges(*inputs):
    """
    Merge multiple ranges into a combined stream of ranges.

    This function combines all inputs by generating "change events" for
    each individual input, and merging these together in a single stream
    of changes. It then groups the change stream by position, and
    iterates over it, yielding a snapshot at each change point.
    """

    def generate_changes(it, input_id):
        for begin, end, data in it:
            assert begin <= end
            yield begin, EVENT_TYPE_BEGIN, input_id, data
            yield end + 1, EVENT_TYPE_END, input_id, None

    changes_generators = [
        generate_changes(input, input_id)
        for input_id, input in enumerate(inputs)
    ]
    all_changes = heapq.merge(*changes_generators)
    grouper = itertools.groupby(all_changes, operator.itemgetter(0))
    active = {}
    previous_position = None

    for position, changes in grouper:

        # Yield output range from the previous position up to the
        # current position, containing all currently valid values.
        if active:
            yield previous_position, position - 1, active.values()

        # Apply begin/end changes
        for _, event_type, input_id, data in changes:
            if event_type == EVENT_TYPE_BEGIN:
                assert input_id not in active
                active[input_id] = data
            elif event_type == EVENT_TYPE_END:
                del active[input_id]

        # Remember current position
        previous_position = position

    # After consuming all changes, all ranges must be closed.
    assert len(active) == 0

#
# Dict diffing and squashing
#

def dict_diff(d, base):
    """
    Calculate differences between a dict and a base dict.

    The return value is a tuple with two items: a `to_set` dictionary
    with all items in `d` that were either unset or changed, and
    a `to_delete` tuple with all removed keys.

    Note: this function does not recursively compare dicts; only the
    keys and values in the supplied dicts will be compared.

    See also dict_patch().
    """
    # The loop below is equivalent to
    #
    #   to_set = dict(d.items() - base.items())
    #
    # ...but the loop below is more performant for dicts with more than
    # a few keys.
    to_set = {}
    for k, v in d.items():
        if not k in base:
            to_set[k] = d[k]  # addition
        elif base[k] != v:
            to_set[k] = v  # mutation

    to_delete = tuple(base.keys() - d.keys())
    return to_set, to_delete

def dict_patch(d, to_set, to_delete):
    """
    Patches a dictionary using a changes dict and a list of deletions.

    This is the reverse of dict_diff().
    """
    d.update(to_set)
    for k in to_delete:
        del d[k]

def dict_diff_decremental(dicts):
    """Create a list of decremental diffs for a sorted list of dicts.

    The input should be sorted from oldest to latest. The output is in
    reverse older, i.e. from latest to oldest. This means that the
    latest version (`dicts[-1]`) is required as the starting point when
    patching.

    For `n` input dicts, `n - 1` reverse diffs will be returned.
    """
    return [
        dict_diff(dicts[i - 1], dicts[i])
        for i in range(len(dicts) - 1, 0, -1)
    ]

def squash_duplicate_dicts(
        dicts, ignored_key=None,
        _ig1=operator.itemgetter(1), _NOT_SET=object()):
    """Deduplicate a list of dicts by squashing adjacent identical dicts.

    This functions takes a list of dicts and returns only the first dict
    of each "run" of identical dicts, in the original order, i.e.
    `[d1, d1, d1, d2, d2, d3, d1]` results in `[d1, d2, d3, d1]`.

    The `ignored_key` arg specifies a key to ignore when comparing the
    dict (which uses a normal `d1 == d2` equality test).
    """
    # Step 1: preparation. Pop (and remember) the key to ignore, but
    # first copy the dicts (instances are "borrowed"), so that we can
    # safely mutate them.
    dicts = [d.copy() for d in dicts]
    transformed = [(d.pop(ignored_key, _NOT_SET), d) for d in dicts]

    # Step 2: deduplication. Group identical information, keeping only
    # the first occurrence of each unique dict. The implementation is
    # based on the unique_justseen() recipe from the itertools docs:
    # group by actual value and take only the first item of each
    # group.
    #
    # Note: the grouper is a generator (lazy), so explicitly turn the
    # result into a list, as the dicts will be modified inside the loop
    # below. Not doing so breaks the comparison inside the grouper.
    squashed = list(map(next, map(_ig1, groupby(transformed, _ig1))))

    # Step 3: transform to original format. Add back the ignored key to obtain
    # dicts in the original format.
    uniques = []
    _append = uniques.append
    for ignored_value, d in squashed:
        if ignored_value is not _NOT_SET:
            # Write back previously extracted ignored key/value (if any)
            d[ignored_key] = ignored_value
        _append(d)

    return uniques

#
# Progress logging
#

DEFAULT_CALLBACK_INTERVAL = 10

class PeriodicCallback(object):
    def __init__(self, cb, interval=DEFAULT_CALLBACK_INTERVAL):
        assert callable(cb)
        self._cb = cb
        self._interval = interval
        self._last_report = float('-inf')

    def tick(self, force_report=False):
        if force_report or time.time() - self._last_report > self._interval:
            self._cb()
            self._last_report = time.time()
<|endoftext|>


<commit_before>from discord.ext import commands
from utils import *
import discord
import asyncio

class Points:

    from member import Member
    #Parse a string to primitave type based on content using lambda expression
    import string
    parseStr = lambda x: x.isalpha() and x or x.isdigit() and \
                int(x) or x.isalnum() and x or \
                len(set(string.punctuation).intersection(x)) == 1 and \
                x.count('.') == 1 and float(x) or x

    def __init__(self,bot):
        self.bot = bot

#Test method to populate an array from discord -Infinite
    @commands.command()
    @commands.has_role('Leadership')
    @asyncio.coroutine
    def getmembers(self):
    #Intialize array
        listOfMembers = []

        #Add members to array
        for member in self.bot.get_all_members():
            listOfMembers.append(Member(parseStr(member.id),member.name,member.nick,member.top_role,0))

        #Get length of array.
        length = len(listOfMembers)

        yield from self.bot.say("NumberofMembersinArray: " + str(length))

def setup(bot):
    bot.add_cog(Points(bot))

<commit_msg>Added typing function.<commit_after>from discord.ext import commands
from utils import *
import discord
import asyncio

class Points:

    from member import Member
    #Parse a string to primitave type based on content using lambda expression
    import string
    parseStr = lambda x: x.isalpha() and x or x.isdigit() and \
                int(x) or x.isalnum() and x or \
                len(set(string.punctuation).intersection(x)) == 1 and \
                x.count('.') == 1 and float(x) or x

    def __init__(self,bot):
        self.bot = bot

#Test method to populate an array from discord -Infinite
    @commands.command()
    @commands.has_role('Leadership')
    @asyncio.coroutine
    def getmembers(self):

    #Typing function
        yield from self.bot.type()
    #Intialize array
        listOfMembers = []

        #Add members to array
        for member in self.bot.get_all_members():
            listOfMembers.append(Member(parseStr(member.id),member.name,member.nick,member.top_role,0))

        #Get length of array.
        length = len(listOfMembers)

        yield from self.bot.say("NumberofMembersinArray: " + str(length))

def setup(bot):
    bot.add_cog(Points(bot))

<|endoftext|>


<commit_before>import asyncio
import base64
import logging

import discord
from discord.ext.commands import cooldown, BucketType
from validators import url as is_url

from bot.bot import command
from bot.globals import Perms
from cogs.cog import Cog
from utils.imagetools import raw_image_from_url
from utils.utilities import (get_emote_url, get_emote_name, send_paged_message, basic_check,
                             create_custom_emoji)
from math import ceil
from bot.converters import PossibleUser

logger = logging.getLogger('debug')

class Server(Cog):
    def __init__(self, bot):
        super().__init__(bot)

    @command(no_pm=True)
    @cooldown(1, 20, type=BucketType.user)
    async def top(self, ctx, page: str='1'):
        """Get the top users on this server based on the most important values"""
        try:
            page = int(page)
            if page <= 0:
                page = 1
        except:
            page = 1

        guild = ctx.guild

        sorted_users = sorted(guild.members, key=lambda u: len(u.roles), reverse=True)
        pages = list(range(1, ceil(len(guild.members)/10)+1))

        def get_msg(page, index):
            s = 'Leaderboards for **{}**\n\n```md\n'.format(guild.name)
            added = 0
            p = page*10
            for idx, u in enumerate(sorted_users[p-10:p]):
                added += 1
                s += '{}. {} with {} roles\n'.format(idx + p-9, u, len(u.roles) - 1)

            if added == 0:
                return 'Page out of range'

            try:
                idx = sorted_users.index(ctx.author) + 1
                s += '\nYour rank is {} with {} roles\n'.format(idx, len(ctx.author.roles) - 1)
            except:
                pass
            s += '```'
            return s

        await send_paged_message(self.bot, ctx, pages, starting_idx=page-1, page_method=get_msg)

    @command(no_dm=True, aliases=['mr_top'])
    @cooldown(2, 5, BucketType.channel)
    async def mute_roll_top(self, ctx, user: PossibleUser=None):
        stats = await self.bot.dbutil.get_mute_roll(ctx.guild.id)
        if not stats:
            return await ctx.send('No mute roll stats on this server')

        page_length = ceil(len(stats)/10)
        pages = [False for _ in range(page_length)]

        title = f'Mute roll stats for guild {ctx.guild}'

        def cache_page(idx, custom_description=None):
            i = idx*10
            rows = stats[i:i+10]
            if custom_description:
                embed = discord.Embed(title=title, description=custom_description)
            else:
                embed = discord.Embed(title=title)

            embed.set_footer(text=f'Page {idx+1}/{len(pages)}')
            for row in rows:
                winrate = round(row['wins'] * 100 / row['games'], 1)
                v = f'<@{row["user"]}>\n' \
                    f'Winrate: {winrate}% with {row["wins"]} wins \n'\
                    f'Current streak: {row["current_streak"]}\n' \
                    f'Biggest streak: {row["biggest_streak"]}'
                embed.add_field(name=f'{row["games"]} games', value=v)

            pages[idx] = embed
            return embed

        def get_page(page, idx):
            if not page:
                return cache_page(idx)

            return page

        i = 0
        if isinstance(user, discord.User):
            user = user.id

        if user:
            for idx, r in enumerate(stats):
                if r['user'] == user:
                    i = idx // 10

                    winrate = round(r['wins'] * 100 / r['games'], 1)
                    d = f'Stats for <@{user}> at page {i+1}\n' \
                        f'Winrate: {winrate}% with {r["wins"]} wins \n' \
                        f'Current streak: {r["current_streak"]}\n' \
                        f'Biggest streak: {r["biggest_streak"]}'

                    cache_page(i, d)
                    break

        await send_paged_message(self.bot, ctx, pages, embed=True, page_method=get_page)

    async def _dl(self, ctx, url):
        try:
            data, mime_type = await raw_image_from_url(url, self.bot.aiohttp_client,
                                                       get_mime=True)
        except OverflowError:
            await ctx.send('Failed to download. File is too big')
        except TypeError:
            await ctx.send('Link is not a direct link to an image')
        else:
            return data, mime_type

    @command(no_pm=True, aliases=['addemote', 'addemoji', 'add_emoji'], required_perms=Perms.MANAGE_EMOJIS)
    @cooldown(2, 6, BucketType.guild)
    async def add_emote(self, ctx, link, *name):
        """Add an emote to the server"""
        guild = ctx.guild
        author = ctx.author

        if is_url(link):
            if not name:
                await ctx.send('What do you want to name the emote as', delete_after=30)
                try:
                    msg = await self.bot.wait_for('message', check=basic_check(author=author, channel=ctx.channel), timeout=30)
                except asyncio.TimeoutError:
                     msg = None
                if not msg:
                    return await ctx.send('Took too long.')
            data = await self._dl(ctx, link)
            name = ' '.join(name)

        else:
            if not ctx.message.attachments:
                return await ctx.send('No image provided')

            data = await self._dl(ctx, ctx.message.attachments[0].url)
            name = link + ' '.join(name)

        if not data:
            return

        data, mime = data
        if 'gif' in mime:
            fmt = 'data:{mime};base64,{data}'
            b64 = base64.b64encode(data.getvalue()).decode('ascii')
            img = fmt.format(mime=mime, data=b64)
            already_b64 = True
        else:
            img = data.getvalue()
            already_b64 = False

        try:
            await create_custom_emoji(guild=guild, name=name, image=img, already_b64=already_b64,
                                      reason=f'{ctx.author} created emote')
        except discord.DiscordException as e:
            await ctx.send('Failed to create emote because of an error\n%s\nDId you check if the image is under 256kb in size' % e)
        except:
            await ctx.send('Failed to create emote because of an error')
            logger.exception('Failed to create emote')
        else:
            await ctx.send('created emote %s' % name)

    @command(no_pm=True, aliases=['trihard'], required_perms=Perms.MANAGE_EMOJIS)
    @cooldown(2, 6, BucketType.guild)
    async def steal(self, ctx, *emoji):
        """Add emotes to this server from other servers.
        Usage:
            {prefix}{name} :emote1: :emote2: :emote3:"""
        if not emoji:
            return await ctx.send('Specify the emotes you want to steal')

        errors = 0
        guild = ctx.guild
        emotes = []
        for e in emoji:
            if errors >= 3:
                return await ctx.send('Too many errors while uploading emotes. Aborting')
            url = get_emote_url(e)
            if not url:
                continue

            animated, name = get_emote_name(e)

            if not name:
                continue

            data = await self._dl(ctx, url)

            if not data:
                continue

            data, mime = data
            if 'gif' in mime:
                fmt = 'data:{mime};base64,{data}'
                b64 = base64.b64encode(data.getvalue()).decode('ascii')
                img = fmt.format(mime=mime, data=b64)
                already_b64 = True
            else:
                img = data.getvalue()
                already_b64 = False

            try:
                emote = await create_custom_emoji(guild=guild, name=name, image=img, already_b64=already_b64,
                                                  reason=f'{ctx.author} stole emote')
                emotes.append(emote)
            except discord.HTTPException as e:
                if e.code == 400:
                    return await ctx.send('Emote capacity reached\n{}'.format(e))
                errors += 1
            except discord.DiscordException as e:
                await ctx.send('Failed to create emote because of an error\n%s' % e)
                errors += 1
            except:
                await ctx.send('Failed to create emote because of an error')
                logger.exception('Failed to create emote')
                errors += 1

        await ctx.send('Successfully stole {}'.format(' '.join(map(lambda e: str(e), emotes))))

def setup(bot):
    bot.add_cog(Server(bot))
<commit_msg>mute roll top command<commit_after>import asyncio
import base64
import logging

import discord
from discord.user import BaseUser
from discord.ext.commands import cooldown, BucketType
from validators import url as is_url

from bot.bot import command
from bot.globals import Perms
from cogs.cog import Cog
from utils.imagetools import raw_image_from_url
from utils.utilities import (get_emote_url, get_emote_name, send_paged_message, basic_check,
                             create_custom_emoji)
from math import ceil
from bot.converters import PossibleUser

logger = logging.getLogger('debug')

class Server(Cog):
    def __init__(self, bot):
        super().__init__(bot)

    @command(no_pm=True)
    @cooldown(1, 20, type=BucketType.user)
    async def top(self, ctx, page: str='1'):
        """Get the top users on this server based on the most important values"""
        try:
            page = int(page)
            if page <= 0:
                page = 1
        except:
            page = 1

        guild = ctx.guild

        sorted_users = sorted(guild.members, key=lambda u: len(u.roles), reverse=True)
        pages = list(range(1, ceil(len(guild.members)/10)+1))

        def get_msg(page, index):
            s = 'Leaderboards for **{}**\n\n```md\n'.format(guild.name)
            added = 0
            p = page*10
            for idx, u in enumerate(sorted_users[p-10:p]):
                added += 1
                s += '{}. {} with {} roles\n'.format(idx + p-9, u, len(u.roles) - 1)

            if added == 0:
                return 'Page out of range'

            try:
                idx = sorted_users.index(ctx.author) + 1
                s += '\nYour rank is {} with {} roles\n'.format(idx, len(ctx.author.roles) - 1)
            except:
                pass
            s += '```'
            return s

        await send_paged_message(self.bot, ctx, pages, starting_idx=page-1, page_method=get_msg)

    @command(no_dm=True, aliases=['mr_top'])
    @cooldown(2, 5, BucketType.channel)
    async def mute_roll_top(self, ctx, user: PossibleUser=None):
        stats = await self.bot.dbutil.get_mute_roll(ctx.guild.id)
        if not stats:
            return await ctx.send('No mute roll stats on this server')

        page_length = ceil(len(stats)/10)
        pages = [False for _ in range(page_length)]

        title = f'Mute roll stats for guild {ctx.guild}'

        def cache_page(idx, custom_description=None):
            i = idx*10
            rows = stats[i:i+10]
            if custom_description:
                embed = discord.Embed(title=title, description=custom_description)
            else:
                embed = discord.Embed(title=title)

            embed.set_footer(text=f'Page {idx+1}/{len(pages)}')
            for row in rows:
                winrate = round(row['wins'] * 100 / row['games'], 1)
                v = f'<@{row["user"]}>\n' \
                    f'Winrate: {winrate}% with {row["wins"]} wins \n'\
                    f'Current streak: {row["current_streak"]}\n' \
                    f'Biggest streak: {row["biggest_streak"]}'
                embed.add_field(name=f'{row["games"]} games', value=v)

            pages[idx] = embed
            return embed

        def get_page(page, idx):
            if not page:
                return cache_page(idx)

            return page

        if isinstance(user, BaseUser):
            user = user.id

        if user:
            for idx, r in enumerate(stats):
                if r['user'] == user:
                    i = idx // 10

                    winrate = round(r['wins'] * 100 / r['games'], 1)
                    d = f'Stats for <@{user}> at page {i+1}\n' \
                        f'Winrate: {winrate}% with {r["wins"]} wins \n' \
                        f'Current streak: {r["current_streak"]}\n' \
                        f'Biggest streak: {r["biggest_streak"]}'

                    cache_page(i, d)
                    break

        await send_paged_message(self.bot, ctx, pages, embed=True, page_method=get_page)

    async def _dl(self, ctx, url):
        try:
            data, mime_type = await raw_image_from_url(url, self.bot.aiohttp_client,
                                                       get_mime=True)
        except OverflowError:
            await ctx.send('Failed to download. File is too big')
        except TypeError:
            await ctx.send('Link is not a direct link to an image')
        else:
            return data, mime_type

    @command(no_pm=True, aliases=['addemote', 'addemoji', 'add_emoji'], required_perms=Perms.MANAGE_EMOJIS)
    @cooldown(2, 6, BucketType.guild)
    async def add_emote(self, ctx, link, *name):
        """Add an emote to the server"""
        guild = ctx.guild
        author = ctx.author

        if is_url(link):
            if not name:
                await ctx.send('What do you want to name the emote as', delete_after=30)
                try:
                    msg = await self.bot.wait_for('message', check=basic_check(author=author, channel=ctx.channel), timeout=30)
                except asyncio.TimeoutError:
                     msg = None
                if not msg:
                    return await ctx.send('Took too long.')
            data = await self._dl(ctx, link)
            name = ' '.join(name)

        else:
            if not ctx.message.attachments:
                return await ctx.send('No image provided')

            data = await self._dl(ctx, ctx.message.attachments[0].url)
            name = link + ' '.join(name)

        if not data:
            return

        data, mime = data
        if 'gif' in mime:
            fmt = 'data:{mime};base64,{data}'
            b64 = base64.b64encode(data.getvalue()).decode('ascii')
            img = fmt.format(mime=mime, data=b64)
            already_b64 = True
        else:
            img = data.getvalue()
            already_b64 = False

        try:
            await create_custom_emoji(guild=guild, name=name, image=img, already_b64=already_b64,
                                      reason=f'{ctx.author} created emote')
        except discord.DiscordException as e:
            await ctx.send('Failed to create emote because of an error\n%s\nDId you check if the image is under 256kb in size' % e)
        except:
            await ctx.send('Failed to create emote because of an error')
            logger.exception('Failed to create emote')
        else:
            await ctx.send('created emote %s' % name)

    @command(no_pm=True, aliases=['trihard'], required_perms=Perms.MANAGE_EMOJIS)
    @cooldown(2, 6, BucketType.guild)
    async def steal(self, ctx, *emoji):
        """Add emotes to this server from other servers.
        Usage:
            {prefix}{name} :emote1: :emote2: :emote3:"""
        if not emoji:
            return await ctx.send('Specify the emotes you want to steal')

        errors = 0
        guild = ctx.guild
        emotes = []
        for e in emoji:
            if errors >= 3:
                return await ctx.send('Too many errors while uploading emotes. Aborting')
            url = get_emote_url(e)
            if not url:
                continue

            animated, name = get_emote_name(e)

            if not name:
                continue

            data = await self._dl(ctx, url)

            if not data:
                continue

            data, mime = data
            if 'gif' in mime:
                fmt = 'data:{mime};base64,{data}'
                b64 = base64.b64encode(data.getvalue()).decode('ascii')
                img = fmt.format(mime=mime, data=b64)
                already_b64 = True
            else:
                img = data.getvalue()
                already_b64 = False

            try:
                emote = await create_custom_emoji(guild=guild, name=name, image=img, already_b64=already_b64,
                                                  reason=f'{ctx.author} stole emote')
                emotes.append(emote)
            except discord.HTTPException as e:
                if e.code == 400:
                    return await ctx.send('Emote capacity reached\n{}'.format(e))
                errors += 1
            except discord.DiscordException as e:
                await ctx.send('Failed to create emote because of an error\n%s' % e)
                errors += 1
            except:
                await ctx.send('Failed to create emote because of an error')
                logger.exception('Failed to create emote')
                errors += 1

        await ctx.send('Successfully stole {}'.format(' '.join(map(lambda e: str(e), emotes))))

def setup(bot):
    bot.add_cog(Server(bot))
<|endoftext|>


<commit_before>import logging
import json
import urllib
import json
import re
from shared import Candidate

def latest_revision(pagename, allow_redirect=True):
	logging.debug("latest_revision(%r)", pagename)
	assert isinstance(pagename, unicode)
	req = urllib.urlopen("https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles=%s&rvprop=content&redirects=%s&format=json" % (urllib.quote(pagename.encode("utf8")), "true" if allow_redirect else "false"))
	page = req.read()
	try:
		data = json.loads(page)
		page_data, = data["query"]["pages"].values()
		revision, = page_data["revisions"]
		return revision["*"]
	except Exception:
		logging.exception("Invalid response for %r", page)
		raise

def parliament_constituencies():
	page = latest_revision(u"List of United Kingdom Parliament constituencies")
	table = re.search(r'class="wikitable sortable"(.*?)\|}\s*==', page, re.DOTALL).group(1)
	after_tr = True
	for line in table.split("\n")[1:]:
		if line.startswith("!"):
			continue
		if line.startswith("|-"):
			after_tr = True
			continue
		if after_tr:
			after_tr = False
			m = re.search(r"^\s*\|\s*\[\[([^\]\|]+)(?:\|[^\]]*)?\]\]", line)
			if m is None:
				raise ValueError(line)
			yield m.group(1).strip()

def section_for_2015(page):
	for section in re.findall(r"\{\{Election box begin.*?\{\{Election box end\}\}", page, re.DOTALL | re.I):
		if "United Kingdom general election, 2015" in section or "Next United Kingdom general election" in section or "|General Election 2015]]" in section:
			assert section.lower().count("election box begin") == 1
			return section
	# does not have any 2015 section
	return None

def candidates_from_section(section):
	# Handles:
	#    {{EBC
	#    | candidate =
	#    | votes =  }}
	# And:
	#    {{EBC
	#    | votes =
	#    }}{{EBC
	#    | votes =
	#    }}
	section = re.sub(r"<!--.*?-->", "", section, flags=re.DOTALL)
	candidates = re.findall(r"\{\{Election box candidate.*?(?:\n\s*\}\}|\}\}\s*\n)", section, re.DOTALL | re.I)
	candidates_check = re.findall(r"candidate\s*=", section, re.DOTALL | re.I)
	if len(candidates_check) != len(candidates):
		raise ValueError((candidates, candidates_check))
	candidates = [parse_candidate_wikitext(c) for c in candidates]
	return [ c for c in candidates if c.name not in {"", "TBA", "TBC"} ]

def remove_wikilink(name):
	m = re.search("^\[\[(.*)\]\]$", name.strip())
	if m is not None:
		return m.group(1).split("|", 1)[-1]
	return name

re_citation_needed = re.compile(r"\{\{(?:citation needed|dubious|verification needed|better source).*?\}\}", flags=re.I)
def remove_references(name):
	name = re.sub(r"<ref.*?</ref>", "", name)
	name = re.sub(r"<ref.*?/>", "", name)
	name = name.strip()
	if name.startswith("[http"):
		name = name.split(" ",1)[-1].replace("]", "", 1)
	name = re_citation_needed.sub("", name)
	name = re.sub(r"\{\{#tag:.*?\}\}", "", name, flags=re.I)
	return name

def parse_candidate_wikitext(wikitext):
	try:
		name = re.search(r"^[\s\|]*candidate\s*=(.*)$", wikitext, re.M).group(1).strip()
	except Exception:
		raise ValueError(wikitext) # name
	name = remove_wikilink(remove_references(name))
	name = name.strip()
	try:
		party = re.search(r"^[\s\|]*party\s*=(.*)$", wikitext, re.M).group(1).strip()
	except Exception:
		raise ValueError(wikitext) # party
	party = remove_wikilink(party)
	party = party.strip()
	references = list(re.findall(r"\b(https?://[^\"'\s<>\[\]]+)[\"'\s<>\[\]]", wikitext))
	citation_needed = re_citation_needed.search(wikitext) is not None
	return Candidate(name, party, person_id=name, party_id=party, references=references, citation_needed=citation_needed)

def fetch_and_parse_candidates(constituency_name):
	try:
		page = latest_revision(constituency_name).encode("utf8")
		section = section_for_2015(page)
		if section is None:
			if "2015" not in page:
				logging.info("No section for 2015 in %r", constituency_name)
				return []
			raise Exception("No section on page")
		return list(candidates_from_section(section))
	except Exception:
		logging.exception("Unable to parse %r", constituency_name)
		return []

if __name__=='__main__':
	logging.root.setLevel(logging.WARN)

	data = {}
	for constituency_name in parliament_constituencies():
		candidates = fetch_and_parse_candidates(constituency_name)
		logging.debug("Found %d candidates for %r", len(candidates), constituency_name)
		data[constituency_name] = candidates

	with open("candidates_from_wikipedia.json", "w") as f:
		json.dump({
			constituency_name : [c.to_dict() for c in candidates]
			for (constituency_name, candidates)
			in data.items() }, f)

<commit_msg>dead link is like citation needed<commit_after>import logging
import json
import urllib
import json
import re
from shared import Candidate

def latest_revision(pagename, allow_redirect=True):
	logging.debug("latest_revision(%r)", pagename)
	assert isinstance(pagename, unicode)
	req = urllib.urlopen("https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles=%s&rvprop=content&redirects=%s&format=json" % (urllib.quote(pagename.encode("utf8")), "true" if allow_redirect else "false"))
	page = req.read()
	try:
		data = json.loads(page)
		page_data, = data["query"]["pages"].values()
		revision, = page_data["revisions"]
		return revision["*"]
	except Exception:
		logging.exception("Invalid response for %r", page)
		raise

def parliament_constituencies():
	page = latest_revision(u"List of United Kingdom Parliament constituencies")
	table = re.search(r'class="wikitable sortable"(.*?)\|}\s*==', page, re.DOTALL).group(1)
	after_tr = True
	for line in table.split("\n")[1:]:
		if line.startswith("!"):
			continue
		if line.startswith("|-"):
			after_tr = True
			continue
		if after_tr:
			after_tr = False
			m = re.search(r"^\s*\|\s*\[\[([^\]\|]+)(?:\|[^\]]*)?\]\]", line)
			if m is None:
				raise ValueError(line)
			yield m.group(1).strip()

def section_for_2015(page):
	for section in re.findall(r"\{\{Election box begin.*?\{\{Election box end\}\}", page, re.DOTALL | re.I):
		if "United Kingdom general election, 2015" in section or "Next United Kingdom general election" in section or "|General Election 2015]]" in section:
			assert section.lower().count("election box begin") == 1
			return section
	# does not have any 2015 section
	return None

def candidates_from_section(section):
	# Handles:
	#    {{EBC
	#    | candidate =
	#    | votes =  }}
	# And:
	#    {{EBC
	#    | votes =
	#    }}{{EBC
	#    | votes =
	#    }}
	section = re.sub(r"<!--.*?-->", "", section, flags=re.DOTALL)
	candidates = re.findall(r"\{\{Election box candidate.*?(?:\n\s*\}\}|\}\}\s*\n)", section, re.DOTALL | re.I)
	candidates_check = re.findall(r"candidate\s*=", section, re.DOTALL | re.I)
	if len(candidates_check) != len(candidates):
		raise ValueError((candidates, candidates_check))
	candidates = [parse_candidate_wikitext(c) for c in candidates]
	return [ c for c in candidates if c.name not in {"", "TBA", "TBC"} ]

def remove_wikilink(name):
	m = re.search("^\[\[(.*)\]\]$", name.strip())
	if m is not None:
		return m.group(1).split("|", 1)[-1]
	return name

re_citation_needed = re.compile(r"\{\{(?:citation needed|dubious|verification needed|better source|dead link).*?\}\}", flags=re.I)
def remove_references(name):
	name = re.sub(r"<ref.*?</ref>", "", name)
	name = re.sub(r"<ref.*?/>", "", name)
	name = name.strip()
	if name.startswith("[http"):
		name = name.split(" ",1)[-1].replace("]", "", 1)
	name = re_citation_needed.sub("", name)
	name = re.sub(r"\{\{#tag:.*?\}\}", "", name, flags=re.I)
	return name

def parse_candidate_wikitext(wikitext):
	try:
		name = re.search(r"^[\s\|]*candidate\s*=(.*)$", wikitext, re.M).group(1).strip()
	except Exception:
		raise ValueError(wikitext) # name
	name = remove_wikilink(remove_references(name))
	name = name.strip()
	try:
		party = re.search(r"^[\s\|]*party\s*=(.*)$", wikitext, re.M).group(1).strip()
	except Exception:
		raise ValueError(wikitext) # party
	party = remove_wikilink(party)
	party = party.strip()
	references = list(re.findall(r"\b(https?://[^\"'\s<>\[\]]+)[\"'\s<>\[\]]", wikitext))
	citation_needed = re_citation_needed.search(wikitext) is not None
	return Candidate(name, party, person_id=name, party_id=party, references=references, citation_needed=citation_needed)

def fetch_and_parse_candidates(constituency_name):
	try:
		page = latest_revision(constituency_name).encode("utf8")
		section = section_for_2015(page)
		if section is None:
			if "2015" not in page:
				logging.info("No section for 2015 in %r", constituency_name)
				return []
			raise Exception("No section on page")
		return list(candidates_from_section(section))
	except Exception:
		logging.exception("Unable to parse %r", constituency_name)
		return []

if __name__=='__main__':
	logging.root.setLevel(logging.WARN)

	data = {}
	for constituency_name in parliament_constituencies():
		candidates = fetch_and_parse_candidates(constituency_name)
		logging.debug("Found %d candidates for %r", len(candidates), constituency_name)
		data[constituency_name] = candidates

	with open("candidates_from_wikipedia.json", "w") as f:
		json.dump({
			constituency_name : [c.to_dict() for c in candidates]
			for (constituency_name, candidates)
			in data.items() }, f)

<|endoftext|>


<commit_before>import json
import os
import time
import argparse
from discord import User
import asyncio
import discord
from bot.bot import command
from datetime import datetime, timedelta
import re
import operator
from utils.utilities import get_emote_name_id, parse_time, datetime2sql, Object
import logging

logger = logging.getLogger('debug')

class Uservote:
    def __init__(self, user):
        self.user = user
        self._vote = None
        self.valid = True

    @property
    def vote(self):
        return self._vote

    @vote.setter
    def vote(self, emote):
        if self._vote is not None:
            self.valid = False

        self._vote = emote

class Poll:
    def __init__(self, bot, message, channel, title, expires_at=None, strict=False,
                 emotes=None, no_duplicate_votes=False, multiple_votes=False):
        """

        Args:
            bot:
            message: either `class`: discord.Message or int
            channel:
            title:
            expires_at:
            strict:
            emotes:
            no_duplicate_votes:
        """
        self._bot = bot
        self.message = message
        self.channel = channel
        self.title = title
        self.expires_at = expires_at
        self.strict = strict
        self.emotes = emotes or []
        self.ignore_on_dupe = no_duplicate_votes
        self.multiple_votes = multiple_votes
        self._task = None

    @property
    def bot(self):
        return self._bot

    def start(self):
        self._task = self.bot.loop.create_task(self._wait())

    def stop(self):
        if self._task:
            try:
                self._task.cancel()
            except:
                pass

    async def _wait(self):
        time_ = self.expires_at - datetime.utcnow()
        time_ = time_.total_seconds()
        if time_ > 0:
            await asyncio.sleep(time_)

        await self.count_votes()

    async def count_votes(self):
        if isinstance(self.message, discord.Message):
            self.message = self.message.id

        try:
            chn = self.bot.get_channel(self.channel)
            msg = await self.bot.get_message(chn, self.message)
        except:
            logger.exception('Failed to end poll')
            channel = self.bot.get_channel(self.channel)
            return await self.bot.send_message(channel, 'Failed to end poll.\nReason: Could not get the poll message')

        votes = {}
        for reaction in msg.reactions:
            if self.strict and str(reaction.emoji) not in self.emotes:
                continue

            users = await self.bot.get_reaction_users(reaction, limit=reaction.count)

            for user in users:
                if user.bot:
                    continue

                if self.ignore_on_dupe and user.id in votes:
                    votes[user.id] = None
                elif not self.multiple_votes:
                    votes[user.id] = [str(reaction.emoji)]
                else:
                    if user.id in votes:
                        votes[user.id] += (str(reaction.emoji), )
                    else:
                        votes[user.id] = [str(reaction.emoji)]

        scores = {}
        for u, reacts in votes.items():
            if reacts is None:
                continue

            for r in reacts:
                score = scores.get(r, 0)
                scores[r] = score + 1

        scores = sorted(scores.items(), key=operator.itemgetter(1))
        biggest = 0

        winners = []
        for emote, score in scores:
            if score >= biggest:
                biggest = score
                winners.append(emote)
            else:
                break

        s = 'Poll ``{}`` ended\nWinner(s) are {} with the score of {}'.format(self.title,
                                                                              ' '.join(winners),
                                                                              biggest)
        await self.bot.send_message(chn, s)

        session = self.bot.get_session

        sql = 'DELETE FROM `polls` WHERE `message`= %s' % self.message
        try:
            session.execute(sql)
            session.commit()
        except:
            logger.exception('Could not delete poll')
            await self.bot.send_message(chn, 'Could not delete poll from database. The result might be recalculated')

class VoteManager:
    def __init__(self, bot):
        self.bot = bot
        self.session = self.bot.get_session
        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('-header', nargs='+')
        self.parser.add_argument('-time', default='60s', nargs='+')
        self.parser.add_argument('-emotes', default=None, nargs='+')
        self.parser.add_argument('-description', default=None, nargs='+')
        self.parser.add_argument('-strict', action='store_true')
        self.parser.add_argument('-no_duplicate_votes', action='store_true')
        self.parser.add_argument('-allow_multiple_entries', action='store_true')

    @command(pass_context=True, owner_only=True, aliases=['vote'])
    async def poll(self, ctx, *, message):
        """
        Create a poll. See help poll for arguments
        Creates a poll that expires by default in 60 seconds
        Examples of use: -poll title -d description -e <:gappyGoodShit:326946656023085056> 👌 -a
        available arguments
        `-d` `-description` Description for the poll
        `-t` `-time` Time after which the poll is expired. Maximum time is 1 week
        `-e` `-emotes` Optional emotes that are automatically added to the poll

        These options require no arguments. Default values that are used when they aren't specified are marked in square brackets []
        `-s` `-strict` [false] Only count emotes specified in the -emotes argument
        `-n` `-no_duplicate_votes` [false] Ignores users who react to more than one emote
        `-a` `-allow_multiple_entries` [false] Count all reactions from the user. Even if that user reacted with multiple emotes.
        """
        # TODO Add permission check

        # Add -header if it's not present so argparser can recognise the argument
        message = '-header ' + message if not message.startswith('-t') else message
        try:
            parsed = self.parser.parse_args(message.split(' '))
        except:
            return await self.bot.say('Failed to parse arguments')

        if parsed.strict and not parsed.emotes:
            return await self.bot.say('Cannot set strict mode without specifying any emotes')

        if parsed.no_duplicate_votes and parsed.allow_multiple_entries:
            return await self.bot.say('Cannot have -n and -a specified at the same time. That would be dumb')

        expired_date = None
        title = ' '.join(parsed.header)
        if parsed.time:
            expires_in = parse_time(' '.join(parsed.time))
            if expires_in.total_seconds() == 0:
                await self.bot.say('No time specified or time given is 0 seconds. Using default value of 60s')
                expires_in = timedelta(seconds=60)
            if expires_in.days > 7:
                return await self.bot.say('Maximum time is 7 days')

            now = datetime.utcnow()
            expired_date = now + expires_in
            sql_date = datetime2sql(expired_date)
            parsed.time = sql_date

        emotes = []
        failed = []
        if parsed.emotes:
            for emote in parsed.emotes:
                if not emote.strip():
                    continue

                name = get_emote_name_id(emote)
                if name is None:
                    emotes.append(emote)
                else:
                    emotes.append(name)

        if parsed.description:
            description = ' '.join(parsed.description)
        else:
            description = discord.Embed.Empty

        embed = discord.Embed(title=' '.join(parsed.header), description=description)
        if parsed.time:
            embed.add_field(name='Expires at',
                            value='{}\nor in {}\nCurrent time{}'.format(parsed.time, str(expires_in), datetime.utcnow().ctime()))
        msg = await self.bot.send_message(ctx.message.channel, embed=embed)

        # add reactions to message
        for emote in emotes:
            try:
                emote = '{}:{}'.format(*emote) if isinstance(emote, tuple) else emote
                await self.bot.add_reaction(msg, emote)
            except:
                failed.append(emote)
        if failed:
            await self.bot.say('Failed to get emotes `{}`'.format('` `'.join(failed)),
                               delete_after=60)

        sql = 'INSERT INTO `polls` (`server`, `strict`, `message`, `expires_in`, `ignore_on_dupe`, `multiple_votes`) ' \
              'VALUES (:server, :strict, :message, :expires_in, :ignore_on_dupe, :multiple_votes)'
        d = {'server': ctx.message.server.id, 'strict': parsed.strict, 'message': msg.id,
             'expires_in': parsed.time, 'ignore_on_dupe': parsed.no_duplicate_votes,
             'multiple_votes':parsed.allow_multiple_entries}
        try:
            self.session.execute(sql, params=d)

            emotes_list = []
            if emotes:
                sql = 'INSERT INTO `emotes` (`name`, `emote`, `server`, `vote_id`) VALUES '
                values = []
                # We add all successfully parsed emotes even if the bot failed to
                # add them so strict mode will count them in too
                for emote in emotes:
                    if not isinstance(emote, tuple):
                        name, id = emote, 'NULL'
                        emotes_list.append(name)
                    else:
                        name, id = emote
                        emotes_list.append('<:{}:{}>'.format(name, id))

                    values.append('("%s", %s, %s, %s)' % (name, id, ctx.message.server.id, msg.id))

                if values:
                    # If emote is already in the table update its name
                    sql += ', '.join(values) + ' ON DUPLICATE KEY UPDATE name=name'
                    self.session.execute(sql)

            self.session.commit()
        except Exception as e:
            logger.exception('Failed sql query')
            return await self.bot.say('Failed to save poll. Exception has been logged')

        poll = Poll(self.bot, msg, msg.channel.id, title, expires_at=expired_date, strict=parsed.strict,
                    emotes=emotes_list, no_duplicate_votes=parsed.no_duplicate_votes,
                    multiple_votes=parsed.allow_multiple_entries)
        poll.start()

    async def get_most_voted(self, msg):
        users_voted = []
        votes = {}
        reactions = msg.reactions
        if not reactions:
            return

        for emote, users in reactions:
            votes_ = 0
            for user in list(users):
                if user not in users_voted:
                    users_voted.append(user)
                    votes_ += 1

            votes[emote] = votes_

        print(votes)
        emote = max(votes.keys(), key=lambda key: votes[key])
        return emote, votes[emote]

    def add_message(self, message):
        votes = self.get_vote_messages(message.server.id)
        votes[message.id] = time.time()

    def get_vote_messages(self, serverid):
        if serverid not in self.votes:
            self.votes[serverid] = {}

        return self.votes[serverid]

def setup(bot):
    bot.add_cog(VoteManager(bot))<commit_msg>added current time to expires in<commit_after>import json
import os
import time
import argparse
from discord import User
import asyncio
import discord
from bot.bot import command
from datetime import datetime, timedelta
import re
import operator
from utils.utilities import get_emote_name_id, parse_time, datetime2sql, Object
import logging

logger = logging.getLogger('debug')

class Uservote:
    def __init__(self, user):
        self.user = user
        self._vote = None
        self.valid = True

    @property
    def vote(self):
        return self._vote

    @vote.setter
    def vote(self, emote):
        if self._vote is not None:
            self.valid = False

        self._vote = emote

class Poll:
    def __init__(self, bot, message, channel, title, expires_at=None, strict=False,
                 emotes=None, no_duplicate_votes=False, multiple_votes=False):
        """

        Args:
            bot:
            message: either `class`: discord.Message or int
            channel:
            title:
            expires_at:
            strict:
            emotes:
            no_duplicate_votes:
        """
        self._bot = bot
        self.message = message
        self.channel = channel
        self.title = title
        self.expires_at = expires_at
        self.strict = strict
        self.emotes = emotes or []
        self.ignore_on_dupe = no_duplicate_votes
        self.multiple_votes = multiple_votes
        self._task = None

    @property
    def bot(self):
        return self._bot

    def start(self):
        self._task = self.bot.loop.create_task(self._wait())

    def stop(self):
        if self._task:
            try:
                self._task.cancel()
            except:
                pass

    async def _wait(self):
        time_ = self.expires_at - datetime.utcnow()
        time_ = time_.total_seconds()
        if time_ > 0:
            await asyncio.sleep(time_)

        await self.count_votes()

    async def count_votes(self):
        if isinstance(self.message, discord.Message):
            self.message = self.message.id

        try:
            chn = self.bot.get_channel(self.channel)
            msg = await self.bot.get_message(chn, self.message)
        except:
            logger.exception('Failed to end poll')
            channel = self.bot.get_channel(self.channel)
            return await self.bot.send_message(channel, 'Failed to end poll.\nReason: Could not get the poll message')

        votes = {}
        for reaction in msg.reactions:
            if self.strict and str(reaction.emoji) not in self.emotes:
                continue

            users = await self.bot.get_reaction_users(reaction, limit=reaction.count)

            for user in users:
                if user.bot:
                    continue

                if self.ignore_on_dupe and user.id in votes:
                    votes[user.id] = None
                elif not self.multiple_votes:
                    votes[user.id] = [str(reaction.emoji)]
                else:
                    if user.id in votes:
                        votes[user.id] += (str(reaction.emoji), )
                    else:
                        votes[user.id] = [str(reaction.emoji)]

        scores = {}
        for u, reacts in votes.items():
            if reacts is None:
                continue

            for r in reacts:
                score = scores.get(r, 0)
                scores[r] = score + 1

        scores = sorted(scores.items(), key=operator.itemgetter(1))
        biggest = 0

        winners = []
        for emote, score in scores:
            if score >= biggest:
                biggest = score
                winners.append(emote)
            else:
                break

        s = 'Poll ``{}`` ended\nWinner(s) are {} with the score of {}'.format(self.title,
                                                                              ' '.join(winners),
                                                                              biggest)
        await self.bot.send_message(chn, s)

        session = self.bot.get_session

        sql = 'DELETE FROM `polls` WHERE `message`= %s' % self.message
        try:
            session.execute(sql)
            session.commit()
        except:
            logger.exception('Could not delete poll')
            await self.bot.send_message(chn, 'Could not delete poll from database. The result might be recalculated')

class VoteManager:
    def __init__(self, bot):
        self.bot = bot
        self.session = self.bot.get_session
        self.parser = argparse.ArgumentParser()
        self.parser.add_argument('-header', nargs='+')
        self.parser.add_argument('-time', default='60s', nargs='+')
        self.parser.add_argument('-emotes', default=None, nargs='+')
        self.parser.add_argument('-description', default=None, nargs='+')
        self.parser.add_argument('-strict', action='store_true')
        self.parser.add_argument('-no_duplicate_votes', action='store_true')
        self.parser.add_argument('-allow_multiple_entries', action='store_true')

    @command(pass_context=True, owner_only=True, aliases=['vote'])
    async def poll(self, ctx, *, message):
        """
        Create a poll. See help poll for arguments
        Creates a poll that expires by default in 60 seconds
        Examples of use: -poll title -d description -e <:gappyGoodShit:326946656023085056> 👌 -a
        available arguments
        `-d` `-description` Description for the poll
        `-t` `-time` Time after which the poll is expired. Maximum time is 1 week
        `-e` `-emotes` Optional emotes that are automatically added to the poll

        These options require no arguments. Default values that are used when they aren't specified are marked in square brackets []
        `-s` `-strict` [false] Only count emotes specified in the -emotes argument
        `-n` `-no_duplicate_votes` [false] Ignores users who react to more than one emote
        `-a` `-allow_multiple_entries` [false] Count all reactions from the user. Even if that user reacted with multiple emotes.
        """
        # TODO Add permission check

        # Add -header if it's not present so argparser can recognise the argument
        message = '-header ' + message if not message.startswith('-t') else message
        try:
            parsed = self.parser.parse_args(message.split(' '))
        except:
            return await self.bot.say('Failed to parse arguments')

        if parsed.strict and not parsed.emotes:
            return await self.bot.say('Cannot set strict mode without specifying any emotes')

        if parsed.no_duplicate_votes and parsed.allow_multiple_entries:
            return await self.bot.say('Cannot have -n and -a specified at the same time. That would be dumb')

        expired_date = None
        title = ' '.join(parsed.header)
        if parsed.time:
            expires_in = parse_time(' '.join(parsed.time))
            if expires_in.total_seconds() == 0:
                await self.bot.say('No time specified or time given is 0 seconds. Using default value of 60s')
                expires_in = timedelta(seconds=60)
            if expires_in.days > 7:
                return await self.bot.say('Maximum time is 7 days')

            now = datetime.utcnow()
            expired_date = now + expires_in
            sql_date = datetime2sql(expired_date)
            parsed.time = sql_date

        emotes = []
        failed = []
        if parsed.emotes:
            for emote in parsed.emotes:
                if not emote.strip():
                    continue

                name = get_emote_name_id(emote)
                if name is None:
                    emotes.append(emote)
                else:
                    emotes.append(name)

        if parsed.description:
            description = ' '.join(parsed.description)
        else:
            description = discord.Embed.Empty

        embed = discord.Embed(title=' '.join(parsed.header), description=description)
        if parsed.time:
            embed.add_field(name='Expires at',
                            value='{}\nor in {}\nCurrent time {}'.format(parsed.time, str(expires_in), datetime.utcnow().ctime()))
        msg = await self.bot.send_message(ctx.message.channel, embed=embed)

        # add reactions to message
        for emote in emotes:
            try:
                emote = '{}:{}'.format(*emote) if isinstance(emote, tuple) else emote
                await self.bot.add_reaction(msg, emote)
            except:
                failed.append(emote)
        if failed:
            await self.bot.say('Failed to get emotes `{}`'.format('` `'.join(failed)),
                               delete_after=60)

        sql = 'INSERT INTO `polls` (`server`, `strict`, `message`, `expires_in`, `ignore_on_dupe`, `multiple_votes`) ' \
              'VALUES (:server, :strict, :message, :expires_in, :ignore_on_dupe, :multiple_votes)'
        d = {'server': ctx.message.server.id, 'strict': parsed.strict, 'message': msg.id,
             'expires_in': parsed.time, 'ignore_on_dupe': parsed.no_duplicate_votes,
             'multiple_votes':parsed.allow_multiple_entries}
        try:
            self.session.execute(sql, params=d)

            emotes_list = []
            if emotes:
                sql = 'INSERT INTO `emotes` (`name`, `emote`, `server`, `vote_id`) VALUES '
                values = []
                # We add all successfully parsed emotes even if the bot failed to
                # add them so strict mode will count them in too
                for emote in emotes:
                    if not isinstance(emote, tuple):
                        name, id = emote, 'NULL'
                        emotes_list.append(name)
                    else:
                        name, id = emote
                        emotes_list.append('<:{}:{}>'.format(name, id))

                    values.append('("%s", %s, %s, %s)' % (name, id, ctx.message.server.id, msg.id))

                if values:
                    # If emote is already in the table update its name
                    sql += ', '.join(values) + ' ON DUPLICATE KEY UPDATE name=name'
                    self.session.execute(sql)

            self.session.commit()
        except Exception as e:
            logger.exception('Failed sql query')
            return await self.bot.say('Failed to save poll. Exception has been logged')

        poll = Poll(self.bot, msg, msg.channel.id, title, expires_at=expired_date, strict=parsed.strict,
                    emotes=emotes_list, no_duplicate_votes=parsed.no_duplicate_votes,
                    multiple_votes=parsed.allow_multiple_entries)
        poll.start()

    async def get_most_voted(self, msg):
        users_voted = []
        votes = {}
        reactions = msg.reactions
        if not reactions:
            return

        for emote, users in reactions:
            votes_ = 0
            for user in list(users):
                if user not in users_voted:
                    users_voted.append(user)
                    votes_ += 1

            votes[emote] = votes_

        print(votes)
        emote = max(votes.keys(), key=lambda key: votes[key])
        return emote, votes[emote]

    def add_message(self, message):
        votes = self.get_vote_messages(message.server.id)
        votes[message.id] = time.time()

    def get_vote_messages(self, serverid):
        if serverid not in self.votes:
            self.votes[serverid] = {}

        return self.votes[serverid]

def setup(bot):
    bot.add_cog(VoteManager(bot))<|endoftext|>