clembot/core/bot.py
def group(*args, **kwargs):
    def decorator(func):
        category = kwargs.get("category")
        func.command_category = category
        examples = kwargs.get("examples")
        func.examples = examples
        error_wrapped_func = wrap_error(func)
        result = commands.group(*args, **kwargs)(error_wrapped_func)
        return result
    return decorator
clembot/exts/bingo/bingo_data_generator.py
class BingoDataGenerator:

    def __init__(self):
        self._cache = {}
        self._new_cache = {}



    pokemon_cp_level = {}

    def cache(self, event_pokemon):

        if event_pokemon in self._cache.keys():
            return self._cache.get(event_pokemon, {})

        self.load_pokemon_data()
        if event_pokemon in self._cache.keys():
            return self._cache.get(event_pokemon, {})

    gender_master = { 'mareep' : [u"\u2642" , u"\u2642", u"\u2642", u"\u2642", u"\u2640", u"\u2640",u"\u2640",u"\u2640"] ,
                      'charmander' : [u"\u2642" , u"\u2642", u"\u2642", u"\u2640",u"\u2642",u"\u2642",u"\u2642",u"\u2642"],
                      'squirtle' : [u"\u2642" , u"\u2642", u"\u2642", u"\u2640",u"\u2642",u"\u2642",u"\u2642",u"\u2642"],
                      'eevee' : [u"\u2642" , u"\u2642", u"\u2642", u"\u2640",u"\u2642",u"\u2642",u"\u2642",u"\u2642"],
                      'chikorita' : [u"\u2642" , u"\u2642", u"\u2642", u"\u2640",u"\u2642",u"\u2642",u"\u2642",u"\u2642"],
                      'beldum': ["", "", "", "", "", "", "", ""],
                      'cyndaquil' : [u"\u2642" , u"\u2642", u"\u2642", u"\u2640",u"\u2642",u"\u2642",u"\u2642",u"\u2642"],
                      'totodile' : [u"\u2642" , u"\u2642", u"\u2642", u"\u2640",u"\u2642",u"\u2642",u"\u2642",u"\u2642"],
                      'piplup': [u"\u2642", u"\u2642", u"\u2642", u"\u2640", u"\u2642", u"\u2642", u"\u2642", u"\u2642"],
                      'rhyhorn': [u"\u2642", u"\u2640", u"\u2642", u"\u2640", u"\u2642", u"\u2640", u"\u2642", u"\u2640"]
                    }


    box_pokemon = [ 'free', 'seedot', 'rhyhorn', 'abra' , 'piplup' , 'seedot', 'charmander', 'gastly', 'porygon', 'weedle' ]

    box_5_pokemon = [ 'free' ]

    @classmethod
    def generate_mixed_card (cls):


        MALE_SIGN = u"\u2642"
        FEMALE_SIGN = u"\u2640"

        stats = ['Attack', 'Defense', 'Stamina']

        category = []
        size = ['XL', 'XL', 'XL', 'XL', 'XS', 'XS', 'XS', 'XS']
        # gender = gender_master.get(event_pokemon,[u"\u2642" , u"\u2642", u"\u2642", u"\u2642", u"\u2640", u"\u2640",u"\u2640",u"\u2640"])

        bingo_card = cls.generate_default_card()

        weight_boxes = [randint(1, 2), randint(6, 7)]
        height_boxes = [randint(8, 9), randint(3, 4)]
        

        for bingo_cell in list(bingo_card.keys()) :
            range_multiplier = 1
            box_pokemon_name = self.box_pokemon[int(bingo_cell)]
            if int(bingo_cell) == 5:
                range_multiplier = 1.25
            elif int(bingo_cell) in weight_boxes or int(bingo_cell) in height_boxes :
                range_multiplier = 1.6

            cell_level = randint(int(randint(10, 12)*range_multiplier), randint(22, 28))
            cell_json = BingoDataGenerator.pokemon_cp_level.get(box_pokemon_name)[f"{cell_level}"]
            cell_cp = randint(cell_json['Min CP'], cell_json['Max CP'])
            cell_range = keep_number_in_range(cell_cp, int(randint(120, 160) * range_multiplier), cell_json['Min CP'], cell_json['Max CP'])
            cell_value = ["CP: {:>0}".format(cell_range)]

            bingo_card[bingo_cell] = cell_value


        for box in weight_boxes:
            bingo_card[f"{box}"] = [bingo_card[f"{box}"][0], f"Weight = {size[randint(0, 7)]}".format()]
        for box in height_boxes:
            bingo_card[f"{box}"] = [bingo_card[f"{box}"][0], f"Height = {size[randint(0, 7)]}".format()]
        bingo_card['5'] = ['']


        return bingo_card

    @classmethod
    def load_pokemon_data(cls, pokemon=None):

        if not pokemon or pokemon in ['dec2018', 'dec2019', 'dec2020']:
            list_of_pokemon = self.box_pokemon
        else:
            list_of_pokemon = [pokemon]

        for pokemon in list_of_pokemon:
            if pokemon == 'free' or pokemon == 'shiny':
                continue
            level_json = {}
            for level in range(1, 31):
                level_json.update(
                    { f"{level}": {
                    "level": level,
                    "Max CP": PokemonDataProvider.cp(pokemon, level, 15, 15, 15),
                    "Min CP": PokemonDataProvider.cp(pokemon, level, 0, 0, 0),
                    "Spread" : int ((PokemonDataProvider.cp(pokemon, level, 15, 15, 15) - PokemonDataProvider.cp(pokemon, level, 0, 0, 0)) / 6) + 1
                }
                })

            # print(json.dumps(level_json, indent=2))
            cls.pokemon_cp_level[pokemon] = level_json

    @classmethod
    def generate_card(cls, event_pokemon='ralts'):


        if event_pokemon not in BingoDataGenerator.pokemon_cp_level.keys():
            BingoDataGenerator.load_pokemon_data(event_pokemon)

        if event_pokemon in ['dec2018', 'dec2019', 'dec2020']:
            return self.generate_mixed_card()


        pokemon_cp = BingoDataGenerator.pokemon_cp_level.get(event_pokemon, self.pokemon_cp_level[f'{event_pokemon}'])


        MALE_SIGN = u"\u2642"
        FEMALE_SIGN = u"\u2640"

        size = ['XL', 'XL', 'XL', 'XL', 'XS', 'XS', 'XS', 'XS']
        gender = BingoDataGenerator.gender_master.get(event_pokemon, [MALE_SIGN, MALE_SIGN, MALE_SIGN, MALE_SIGN, FEMALE_SIGN, MALE_SIGN, MALE_SIGN, MALE_SIGN])

        bingo_card = {}

        cell_1_level = randint(6,9)
        cell_1_json = pokemon_cp[f"{cell_1_level}"]
        cell_1_cp = randint(cell_1_json['Min CP'], cell_1_json['Max CP'])
        cell_1_range = keep_number_in_range(cell_1_cp, cell_1_json['Spread'], cell_1_json['Min CP'],cell_1_json['Max CP'])
        cell_1_value = ["CP:{:>0}".format(cell_1_range)]

        cell_2_level = randint(4,14)
        cell_2_json = pokemon_cp[f"{cell_2_level}"]
        cell_2_low_json = pokemon_cp["{0}".format(cell_2_level - 2)]
        cell_2_high_json = pokemon_cp["{0}".format(cell_2_level + 2)]
        cell_2_cp = randint(cell_2_json['Min CP'], cell_2_json['Max CP'])
        cell_2_spread = ( pokemon_cp["12"]["Spread"] + pokemon_cp["13"]["Spread"] + pokemon_cp["14"]["Spread"] ) * 2
        cell_2_range = keep_number_in_range(cell_2_cp, cell_2_spread, cell_2_low_json['Min CP'], cell_2_high_json['Max CP'])
        cell_2_value = [f"CP: {cell_2_range}", f"Weight = {size[randint(0, 7)]}"]

        cell_3_level = randint(18,21)
        cell_3_json = pokemon_cp[f"{cell_3_level}"]
        cell_3_cp = randint(cell_3_json['Min CP'], cell_3_json['Max CP'])
        cell_3_range = keep_number_in_range(cell_3_cp, cell_3_json['Spread'], cell_3_json['Min CP'],cell_3_json['Max CP'])
        cell_3_value = [f"CP : {cell_3_range} "]


        cell_4_level = randint(10,13)
        cell_4_json = pokemon_cp[f"{cell_4_level}"]
        cell_4_cp = randint(cell_4_json['Min CP'], cell_4_json['Max CP'])
        cell_4_range = keep_number_in_range(cell_4_cp, cell_4_json['Spread'] * 2, cell_4_json['Min CP'], cell_4_json['Max CP']) #cell_4_json['Spread'] * 2
        cell_4_value = [f"CP:{cell_4_range} ", f"{gender[randint(0,7)]}"] #

        cell_5_value = [ "" , ""]
        # ["{0}".format(event_pokemon.capitalize().center(13, ' ')), "✩"]

        cell_6_level = randint(22, 25)
        cell_6_json = pokemon_cp[f"{cell_6_level}"]
        cell_6_cp = randint(cell_6_json['Min CP'], cell_6_json['Max CP'])
        cell_6_range = keep_number_in_range(cell_6_cp, cell_6_json['Spread'] * 2, cell_6_json['Min CP'], cell_6_json['Max CP']) #cell_6_json['Spread'] * 2
        cell_6_value = [f"CP:{cell_6_range} ", f"{gender[randint(0, 7)]}"]


        cell_7_level = randint(14, 17)
        cell_7_json = pokemon_cp[f"{cell_7_level}"]
        cell_7_cp = randint(cell_7_json['Min CP'], cell_7_json['Max CP'])
        cell_7_range = keep_number_in_range(cell_7_cp, cell_7_json['Spread'], cell_7_json['Min CP'], cell_7_json['Max CP'])
        cell_7_value = [f"CP:{cell_7_range} "]


        cell_8_level = randint(16,26)
        cell_8_json = pokemon_cp[f"{cell_8_level}"]
        cell_8_low_json = pokemon_cp["{0}".format(cell_8_level - 1)]
        cell_8_high_json = pokemon_cp["{0}".format(cell_8_level + 1)]
        cell_8_cp = randint(cell_8_json['Min CP'], cell_8_json['Max CP'])
        cell_8_spread = (pokemon_cp["8"]["Spread"] + pokemon_cp["9"]["Spread"] + pokemon_cp["10"]["Spread"]) * 2
        cell_8_range = keep_number_in_range(cell_8_cp, cell_8_spread, cell_8_low_json['Min CP'], cell_8_high_json['Max CP'])
        cell_8_value = [f"CP:{cell_8_range}", f"Height = {size[randint(0, 7)]}"]

        cell_9_level = randint(26, 30)
        cell_9_json = pokemon_cp[f"{cell_9_level}"]
        cell_9_cp = randint(cell_9_json['Min CP'], cell_9_json['Max CP'])
        cell_9_range = keep_number_in_range(cell_9_cp, cell_9_json['Spread'], cell_9_json['Min CP'],cell_9_json['Max CP'])
        cell_9_value = [f"CP:{cell_9_range} "]


        bingo_card['1'] = cell_1_value
        bingo_card['2'] = cell_2_value
        bingo_card['3'] = cell_3_value
        bingo_card['4'] = cell_4_value
        bingo_card['5'] = cell_5_value
        bingo_card['6'] = cell_6_value
        bingo_card['7'] = cell_7_value
        bingo_card['8'] = cell_8_value
        bingo_card['9'] = cell_9_value

        return bingo_card



    @classmethod
    def generate_default_card(cls):

        bingo_card = {}

        bingo_card['1'] = 'Level 2 , 30 CP'
        bingo_card['2'] = 'Level 2 , 30 CP'
        bingo_card['3'] = 'Level 2 , 30 CP'
        bingo_card['4'] = 'Level 2 , 30 CP'
        bingo_card['5'] = 'Level 2 , 30 CP'
        bingo_card['6'] = 'Level 2 , 30 CP'
        bingo_card['7'] = 'Level 2 , 30 CP'
        bingo_card['8'] = 'Level 2 , 30 CP'
        bingo_card['9'] = 'Level 2 , 30 CP'

        return bingo_card

    @classmethod
    def print_card(cls, bingo_card):
        print("")

        print("|\t{0}\t|\t{1}\t|\t{2}\t|".format(bingo_card['1'],bingo_card['2'],bingo_card['3']))
        print("|\t{0}\t|\t\t{1}\t\t\t|\t{2}\t|".format(bingo_card['4'], bingo_card['5'], bingo_card['6']))
        print("|\t{0}\t|\t{1}\t|\t{2}\t|".format(bingo_card['7'], bingo_card['8'], bingo_card['9']))


    @classmethod
    def print_card_as_text(cls, bingo_card):

        text = "|\t{0}\t|\t{1}\t|\t{2}\t|".format(bingo_card['1'],bingo_card['2'],bingo_card['3'])
        text = text + "\n"+ "|\t{0}\t|\t{1}\t|\t{2}\t|".format(bingo_card['4'], bingo_card['5'], bingo_card['6'])
        text = text + "\n" +"|\t{0}\t|\t{1}\t|\t{2}\t|".format(bingo_card['7'], bingo_card['8'], bingo_card['9'])

        return text
clembot/utilities/utils/utilities.py
class Utilities:

    numbers = {"0": ":zero:", "1": ":one:", "2": ":two:", "3": ":three:", "4": ":four:", "5": ":five:", "6": ":six:", "7": ":seven:", "8": ":eight:", "9": ":nine:"}

    @staticmethod
    def trim_to(text, length, delimiter=","):
        if len(text) == 0:
            return "None"
        if text and delimiter:
            return text[:text.rfind(delimiter, 0, length)] + " ** and more.**" if len(text) > length else text
        return text

    @staticmethod
    def emojify_numbers(number):
        number_emoji = ""

        reverse = "".join(reversed(str(number)))

        for digit in reverse[::-1]:

            emoji = Utilities.numbers.get(digit)
            if not emoji:
                emoji = ":regional_indicator_" + digit.lower() + ":"

            number_emoji = number_emoji + emoji

        return number_emoji

    @staticmethod
    def _normalize(emoji):
        initial_emoji = emoji
        if isinstance(emoji, discord.Reaction):
            emoji = emoji.emoji

        if isinstance(emoji, discord.Emoji):
            emoji = ':%s:%s' % (emoji.name, emoji.id)

        elif isinstance(emoji, discord.PartialEmoji):
            emoji = emoji._as_reaction()
        elif isinstance(emoji, str):
            pass

        if emoji.count(':') == 1 and not emoji.startswith(':'):
            emoji = f":{emoji}"

        if emoji.__contains__(">") and emoji.__contains__("<"):
            emoji = emoji.replace('<','').replace('>','')
        return emoji


    @staticmethod
    def _demojify(emoji):
        # convert emoji to id
        if isinstance(emoji, discord.Reaction):
            emoji = emoji.emoji.id

        if isinstance(emoji, discord.Emoji):
            emoji = emoji.id
        elif isinstance(emoji, discord.PartialEmoji):
            emoji = emoji.id if emoji.id else emoji.name
        elif isinstance(emoji, str):
            pass

        return emoji

    @staticmethod
    def _emojify(emoji):
        if emoji.__contains__(">") and emoji.__contains__("<"):
            emoji = emoji.replace('<', '').replace('>', '')
        return emoji

    @staticmethod
    def _uuid(cls, id):
        try:
            return '%x' % (hash(id) % 10 ** 8)
        except Exception as error:
            Logger.error(error)
            return id

    @staticmethod
    async def _send_error_message(channel, description, user=None):

        color = discord.Colour.red()
        user_mention = ""
        if user:
            user_mention = f"Beep Beep! **{user.display_name}** "
        error_embed = discord.Embed(description=f"{user_mention}{description}", colour=color)
        return await channel.send(embed=error_embed)

    @staticmethod
    async def message(destination, description, user=None):

        color = discord.Colour.green()
        user_mention = ""
        if user:
            user_mention = f"Beep Beep! **{user.display_name}** "
        error_embed = discord.Embed(description=f"{user_mention}{description}", colour=color)
        return await destination.send(embed=error_embed)

    @staticmethod
    async def message_as_text(channel, description):
        return await channel.send(description)

    @staticmethod
    async def error(channel, description, user=None):

        color = discord.Colour.red()
        user_mention = ""
        if user:
            user_mention = f"Beep Beep! **{user.display_name}** "
        error_message = f"{user_mention}{description}"
        error_embed = discord.Embed(description=f"{error_message}", colour=color)
        Logger.error(error_message)
        return await channel.send(embed=error_embed)

    @staticmethod
    async def _send_message(channel, description, title=None, footer=None, user=None):
        try:

            error_message = "The output contains more than 2000 characters."

            user_mention = ""
            if user:
                user_mention = f"Beep Beep! **{user.display_name}** "

            if len(description) >= 2000:
                discord.Embed(description="{0}".format(error_message))

            color = discord.Colour.green()
            message_embed = discord.Embed(description=f"{user_mention}{description}", colour=color, title=title)
            if footer:
                message_embed.set_footer(text=footer)
            return await channel.send(embed=message_embed)
        except Exception as error:
            Logger.error(error)

    @staticmethod
    async def _send_embed(channel, description=None, title=None, additional_fields={}, footer=None):

        embed = discord.Embed(description=description, colour=discord.Colour.gold(), title=title)

        for label, value in additional_fields.items():
            if value:
                embed.add_field(name="**{0}**".format(label), value=value, inline=False)

        if footer:
            embed.set_footer(text=footer)

        try:
            return await channel.send(embed=embed)
        except Exception as error:
            return await channel.send(error)

    @command(name="export")
    async def _export(self, ctx):

        return await self._send_message(ctx.channel, "Beep Beep! **{}**, This feature is under-development!".format(ctx.message.author.display_name))

        Logger.error("_export() called!")

        raid_dict = ctx.bot.guild_dict[ctx.guild.id]['raidchannel_dict'][ctx.channel.id]

        channel_mentions = ctx.message.raw_channel_mentions

        if len(channel_mentions) < 1:
            await self._send_error_message(ctx.channel, "Beep Beep! **{}**, Please provide the channel reference to export the details!".format(ctx.message.author.display_name))

    @command(name="clean_content")
    async def _clean_content(self, message):

        message_content = {}
        content_without_mentions = message.content

        for mention in message.mentions:
            mention_text = mention.mention.replace("!", "")
            content_without_mentions = content_without_mentions.replace("<@!", "<@").replace(mention_text, '')

        # remove extra spaces
        message_content['content_without_mentions'] = re.sub(' +', ' ', content_without_mentions)

        return message_content

    @staticmethod
    def get_help_embed(description, usage, available_value_title, available_values, mode="message"):

        if mode == "message":
            color = discord.Colour.green()
        else:
            color = discord.Colour.red()

        help_embed = discord.Embed( description="**{0}**".format(description), colour=color)

        help_embed.add_field(name="**Usage :**", value = "**{0}**".format(usage))
        help_embed.add_field(name="**{0} :**".format(available_value_title), value=_("**{0}**".format(", ".join(available_values))), inline=False)

        return help_embed

    @staticmethod
    async def _send_error_message_and_cleanup(channel, message, user):
        log_message = await Embeds.error(channel, message, user=user)
        await asyncio.sleep(8)
        await log_message.delete()


    @staticmethod
    async def get_image_embed(channel, image_url):
        embed = discord.Embed(colour=channel.guild.me.colour)
        embed.set_thumbnail(url=image_url)
        return await channel.send(embed=embed)

    @staticmethod
    async def ask(message, destination, user_list=None, *, react_list=['✅', '❎']):
        if user_list and type(user_list) != __builtins__.list:
            user_list = [user_list]


        def check(reaction, user):
            if user_list and type(user_list) is __builtins__.list:
                return (user.id in user_list) and (reaction.message.id == message.id) and (reaction.emoji in react_list)
            elif not user_list:
                return (user.id != message.guild.me.id) and (reaction.message.id == message.id) and (reaction.emoji in react_list)


        for r in react_list:
            await asyncio.sleep(0.25)
            await message.add_reaction(r)
        try:
            reaction, user = await Clembot.wait_for('reaction_add', check=check, timeout=60)
            return reaction, user
        except asyncio.TimeoutError:
            await message.clear_reactions()
            return


    @staticmethod
    @wrap_error
    async def ask_confirmation(ctx, message, rusure_message, yes_message, no_message, timed_out_message):
        author = message.author
        channel = message.channel

        reaction_list = ['✅', '❎']
        # reaction_list = ['❔', '✅', '❎']

        rusure = await ctx.channel.send(f"Beep Beep! {rusure_message}")
        await rusure.add_reaction( "✅")  # checkmark
        await rusure.add_reaction( "❎")  # cross

        def check(react, user):
            if user.id != author.id:
                return False
            return True

        # res = await Clembot.wait_for_reaction(reaction_list, message=rusure, check=check, timeout=60)
        try:
            reaction, user = await ctx.bot.wait_for('reaction_add', check=check, timeout=10)
        except asyncio.TimeoutError:
            await rusure.delete()
            confirmation = await channel.send(f"Beep Beep! {timed_out_message}")
            await asyncio.sleep(1)
            await confirmation.delete()
            return False

        if reaction.emoji == "❎":
            await rusure.delete()
            confirmation = await channel.send(f"Beep Beep! {no_message}")
            await asyncio.sleep(1)
            await confirmation.delete()
            return False
        elif reaction.emoji == "✅":
            await rusure.delete()
            confirmation = await channel.send(f"Beep Beep! {yes_message}")
            await asyncio.sleep(1)
            await confirmation.delete()
            return True


    @staticmethod
    @wrap_error
    async def ask_via_reactions(ctx, message, question_message, accepted_message, rejected_message, timed_out_message, reaction_dict, error_response):
        author = message.author
        channel = message.channel

        rusure = await ctx.channel.send(f"{question_message}")

        for r in reaction_dict.keys():
            await rusure.add_reaction(r)

        def check(react, user):
            if user.id != author.id:
                return False
            return True

        try:
            reaction, user = await ctx.bot.wait_for('reaction_add', check=check, timeout=10)
        except asyncio.TimeoutError:
            await rusure.delete()
            confirmation = await channel.send(f"{timed_out_message}")
            await asyncio.sleep(1)
            await confirmation.delete()
            return error_response

        if reaction.emoji not in reaction_dict.keys():
            await rusure.delete()
            confirmation = await channel.send(f"{rejected_message}")
            await asyncio.sleep(1)
            await confirmation.delete()
            return error_response

        elif reaction.emoji in reaction_dict.keys():
            await rusure.delete()
            confirmation = await channel.send(f"{accepted_message}")
            await asyncio.sleep(1)
            await confirmation.delete()
            return reaction_dict.get(reaction.emoji, error_response)


    @staticmethod
    async def send_to_hastebin(destination, whatever):
        whatever = whatever.encode('ascii', errors='replace').decode('utf-8')
        print(whatever)
clembot/exts/bingo/bingo_card_manager.py
class BingoCardManager:

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

    async def find_bingo_card(self, guild_id, user_id, event):

        try:
            Logger.info(f"find_bingo_card({guild_id}, {user_id}, {event})")

            bingo_card_table = self.dbi.table('bingo_card')

            bingo_card_query = bingo_card_table.query().select().where(guild_id=guild_id, user_id=user_id, event=event)

            bingo_record = await bingo_card_query.get_first()
            if bingo_record:
                return bingo_record
        except Exception as error:
            Logger.info(error)

        return None

    async def save_bingo_card(self, guild_id, user_id, event, bingo_card, bingo_card_url, generated_at):
        Logger.info(f"save_bingo_card ({guild_id}, {user_id}, {event})")
        try:

            bingo_card_record = {
                "guild_id": guild_id,
                "user_id": user_id,
                "event":event,
                "bingo_card": json.dumps(bingo_card),
                "bingo_card_url": bingo_card_url,
                "generated_at": generated_at
            }

            table = self.dbi.table('bingo_card')

            existing_bingo_card = await table.query().select().where(guild_id=guild_id, user_id=user_id, event=event).get_first()

            if existing_bingo_card:
                update_query = table.update(bingo_card=json.dumps(bingo_card), bingo_card_url=bingo_card_url, generated_at=generated_at).where(user_id=user_id, guild_id=guild_id, event=event)
                await update_query.commit()
            else:
                insert_query = table.insert(**bingo_card_record)
                await insert_query.commit()

        except Exception as error:
            Logger.error(f"{traceback.format_exc()}")

        return
clembot/exts/bingo/bingo_card_writer.py
class BingoCardWriter:

    @staticmethod
    def generate_board(user_name, bingo_card = None, template_file=None):
        board_layout = BingoCardWriter.generate_board_layout(bingo_card)
        board_image = BingoCardWriter.generate_board_image(board_layout, user_name=user_name, template_file=template_file)
        return board_image


    @staticmethod
    def generate_board_layout(bingo_card = None):
        board = []
        row = []

        row.append(bingo_card['1'])
        row.append(bingo_card['2'])
        row.append(bingo_card['3'])

        board.append(row)

        row = []

        row.append(bingo_card['4'])
        row.append(bingo_card['5'])
        row.append(bingo_card['6'])

        board.append(row)

        row = []

        row.append(bingo_card['7'])
        row.append(bingo_card['8'])
        row.append(bingo_card['9'])

        board.append(row)

        return board


    @staticmethod
    def generate_board_image(bingo_board,  file_name='bingo', user_name='anon', template_file="dec2019.png"):

        try:
            script_path = os.path.dirname(os.path.realpath(__file__))
            dir_path = os.path.join(script_path)
            file_path = os.path.join(script_path, "templates")
            y_position = 190
            cell_width =  155
            cell_height = 110
            margin = 10
            font = ImageFont.truetype(os.path.join(script_path,"fonts","Helvetica-Bold.ttf"), 10, encoding="unic")
            special_font = ImageFont.truetype(os.path.join(script_path, "fonts","DejaVuSansMono.ttf"), 40, encoding="unic")
            small_font = ImageFont.truetype(os.path.join(script_path, "fonts","Helvetica-Bold.ttf"), 18, encoding="unic")

            path  = os.path.join(file_path, template_file)

            canvas = Image.open(os.path.join(file_path, template_file))
            draw = ImageDraw.Draw(canvas)

            colors = ['indigo','white','indigo','white','indigo','white','indigo','white','indigo']
            counter = 0
            for row in bingo_board:
                x_position = 45
                for cell in row:

                    if len(cell) == 1:
                        text = textwrap.fill(cell[0], 12)
                        draw.text((x_position, y_position), text, colors[counter], small_font)
                        # print(f'[1]{x_position},{y_position}=>{text}')
                    else:
                        text = textwrap.fill(cell[0], 12)
                        draw.text((x_position, y_position - 15 ), text, colors[counter], small_font)
                        # print(f'[2]{x_position},{y_position}=>{text}')
                        if len(cell[1]) == 1:
                            text = textwrap.fill(cell[1], 12)
                            draw.text((x_position + 35, y_position + 10 ), text, colors[counter], special_font)
                            # print(f'[3]{x_position},{y_position}=>{text}')
                        else:
                            text = textwrap.fill(cell[1], 12)
                            draw.text((x_position, y_position + 20), text, colors[counter], small_font)
                            # print(f'[4]{x_position},{y_position}=>{text}')
                    counter=counter+1

                    x_position += cell_width
                y_position += cell_height

            rand_file_int = randint(0, 1333337);
            file_name = os.path.join(dir_path,'bingo_boards', file_name + '_' + str(user_name) + '_' + str(rand_file_int) + '.png')
            canvas.save(file_name, "PNG", quality=20, optimize=True)


        except Exception as error:
            Logger.error(f"{traceback.format_exc()}")
        return file_name
clembot/exts/config/guild_metadata.py
class GuildMetadata:


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

    def __eq__(self, other):
        return self.guild.id == other.guild.id

    @property
    def _data(self):
        report_guild_query = self.bot.dbi.table('guild_metadata').query()
        _data = report_guild_query.where(guild_id=self.guild.id)
        return _data


    @classmethod
    async def data(cls, bot, guild_id, config_name):

        report_guild_query = bot.dbi.table('guild_metadata').query()
        _data = report_guild_query.where(guild_id=guild_id, config_name=config_name)
        db_record = await _data.get()

        if db_record:
            guild_metadata = GuildMetadata.deserialize(dict(db_record[0]))
            return guild_metadata

        return {}

    @classmethod
    async def city(cls, bot, guild_id):
        guild_dict = await GuildMetadata.data(bot, guild_id, 'city')
        return guild_dict.get('config_value')

    @classmethod
    async def bingo_card_repo(cls, bot, guild_id):
        guild_dict = await GuildMetadata.data(bot, guild_id, 'bingo-card-repo') or {}
        return guild_dict.get('config_value')

    @staticmethod
    def serialize(data_dict):
        return _.map_values(data_dict, lambda val: json.dumps(val) if isinstance(val, dict) else val)

    @staticmethod
    def deserialize(data_dict):
        return { k :json.loads(v) if k == 'config' and v is not None else v for (k, v) in data_dict.items()}


    @classmethod
    async def update(cls, bot, guild_dict):
        guild_metadata_table = bot.dbi.table('guild_metadata')
        update_dict=GuildMetadata.serialize(guild_dict)
        guild_metadata_table_update = guild_metadata_table.update(**update_dict).where(guild_id=guild_dict.get('guild_id'))
        await guild_metadata_table_update.commit()
        GuildMetadata.cache(guild_dict)

    @classmethod
    async def insert(cls, bot, guild_dict):
        guild_metadata_table = bot.dbi.table('guild_metadata')
        update_dict = GuildMetadata.serialize(guild_dict)
        guild_metadata_table_insert = guild_metadata_table.insert(**update_dict)
        await guild_metadata_table_insert.commit()
        GuildMetadata.cache(guild_dict)

    @staticmethod
    def embed(ctx, guild_dict):
        return (GuildMetadataEmbed.from_guild_metadata(ctx, guild_dict)).embed
clembot/core/errors.py
def wrap_error(func):
    """
    Decorator to handle logging of exception and sending an error message to the user.
    Need ctx object to send an error message to user. if ctx is not the second argument, specify which argument is ctx.
    """
    @wraps(func)
    async def decorator(*args, **kwargs):
        try:
            return await func(*args, **kwargs)

        except AccessDenied as aErr:
            ctx = next(filter(lambda arg: isinstance(arg, Context), args))
            header = check_failure_header[int(str(TH.current_epoch()).split(".")[-1]) % len(check_failure_header)]

            error_message = await ctx.send(embed=Embeds.make_embed(
                header_icon=Icons.INVALID_ACCESS, msg_color=discord.Color.dark_red(),
                header=f"{header}",
                content=f'**Error details:** {aErr}'))

            await error_message.add_reaction('🗑️')
        except BadArgument as bErr:
            ctx = next(filter(lambda arg: isinstance(arg, Context), args))

            header = bad_arguments_header[int(str(TH.current_epoch()).split(".")[-1]) % len(bad_arguments_header)]

            error_message = await ctx.send(embed=Embeds.make_embed(
                header_icon=Icons.error, msg_color=discord.Color.dark_red(),
                header=f"{header}",
                content=f'**Error details:** {bErr}'))

            await error_message.add_reaction('🗑️')
            return

        except ShowErrorMessage as sErr:
            ctx = next(filter(lambda arg: isinstance(arg, Context), args))

            header = user_info_header[int(str(TH.current_epoch()).split(".")[-1]) % len(user_info_header)]

            error_message = await ctx.send(embed=Embeds.make_embed(
                header_icon=Icons.BOT_ERROR_2, msg_color=discord.Color.dark_red(),
                header=f"{header}",
                content=f'{sErr}'))

            await error_message.add_reaction('🗑️')

            pass
        except Exception as error:
            print("--------------------")
            import traceback
            ref_id = f"E-{CUIDGenerator.cuid(int(TH.current_epoch()))}"
            Logger.error(f"{ref_id} - {traceback.format_exc()}")

            ctx = next(filter(lambda arg: isinstance(arg, Context), args))

            if ctx:
                await ctx.send(embed=Embeds.make_embed(
                    header_icon=Icons.BOT_ERROR, msg_color=discord.Color.dark_red(),
                    header=error_header[int(str(TH.current_epoch()).split(".")[-1]) % len(error_header)],
                    content=f'{error_message_description[int(str(TH.current_epoch()).split(".")[-1]) % len(error_message_description)]}\n\n||**Command:** `{ctx.message.content}`\n**Error:** `{error}`\n**Where:** `{func.__name__}()`||',
                    footer=f"Reference Id: {ref_id}"))
            return None

    return decorator
clembot/exts/config/globalconfigmanager.py
class GlobalConfigCache:


    by_config_name = dict()

    def __init__(self, dbi, bot = None):
        self.dbi = dbi
        self.utilities = Utilities()
        self._cache = {}
        if bot:
            self.bot = bot
            self.bot.loop.create_task(self.load_config())

    def get_all_config(self):
        return self._cache


    async def get_clembot_config(self, config_name, reload=False):

        if reload or not self._cache:
            await self.load_config()

        if config_name in self._cache.keys():
            config_value = self._cache.get(config_name, None)
            return config_value

        return None

    async def load_config(self):

        Logger.info(f'load_config()')

        cache = {}
        try:
            clembot_config_tbl = self.dbi.table('clembot_config')
            clembot_query = clembot_config_tbl.query().select()

            config_records = await clembot_query.get()

            for cr in config_records:
                cache[cr['config_name']] = cr['config_value']
                GlobalConfigCache.by_config_name[cr['config_name']] = cr['config_value']

            self._cache.clear()
            self._cache.update(cache)
        except Exception as error:
            Logger.error(error)
        return None

    async def save_clembot_config(self, config_name, config_value):
        try:
            print("save_clembot_config ({0}, {1})".format(config_name, config_value))

            clembot_config_record = {
                "config_name": config_name,
                "config_value": config_value
            }
            table = self.dbi.table('clembot_config')

            existing_config_record = await table.query().select().where(config_name=config_name).get_first()

            if existing_config_record:
                update_query = table.update(config_value=config_value).where(config_name=config_name)
                await update_query.commit()
            else:
                insert_query = table.insert(**clembot_config_record)
                await insert_query.commit()

            await self.load_config()

        except Exception as error:
            Logger.error(f"{traceback.format_exc()}")

    @classmethod
    async def loadconfig(cls, bot):

        Logger.info(f'load_config()')

        cache = {}
        try:
            clembot_config_tbl = bot.dbi.table('clembot_config')
            clembot_query = clembot_config_tbl.query().select()

            config_records = await clembot_query.get()

            for cr in config_records:
                cache[cr['config_name']] = cr['config_value']
                GlobalConfigCache.by_config_name[cr['config_name']] = cr['config_value']

            cls.by_config_name.clear()
            cls.by_config_name.update(cache)
        except Exception as error:
            Logger.error(error)
        return None


    @classmethod
    async def saveclembotconfig(cls, bot , config_name, config_value):
        try:
            print("save_clembot_config ({0}, {1})".format(config_name, config_value))

            clembot_config_record = {
                "config_name": config_name,
                "config_value": config_value
            }
            table = bot.dbi.table('clembot_config')

            existing_config_record = await table.query().select().where(config_name=config_name).get_first()

            if existing_config_record:
                update_query = table.update(config_value=config_value).where(config_name=config_name)
                await update_query.commit()
            else:
                insert_query = table.insert(**clembot_config_record)
                await insert_query.commit()

            await cls.load_config(bot)

        except Exception as error:
            Logger.error(f"{traceback.format_exc()}")
clembot/core/bot.py
def command(*args, **kwargs):
    def decorator(func):
        category = kwargs.get("category")
        examples = kwargs.get("examples")
        func.command_category = category
        func.examples = examples
        error_wrapped_func = wrap_error(func)
        result = commands.command(*args, **kwargs)(error_wrapped_func)
        return result
    return decorator
clembot/utilities/timezone/timehandler.py
_UTC = 'UTC'
_GUILD_TIMEZONE = 'America/New_York'
PST = 'America/Los_Angeles'
def is_tz_aware(dt : datetime) -> bool:
def localize(dt: datetime, tz) -> datetime:
def epoch(date_time: datetime, zone: str = None) -> float:
def current_epoch(second_precision: bool=True, day_precision: bool=False) -> float:
def is_in_future(utc_timestamp: float) -> bool:
def as_local_time(utc_timestamp: float, zone: str=_UTC) -> datetime:
def as_local_readable_time(utc_timestamp: float, zone: str=_UTC) -> str:
def add_time(utc_timestamp: float, delta: timedelta):
def date_timestamp_test():
def get_offset(date_time):
def convert_into_time(time_as_text, require_am_pm=True):
def convert_to_timestamp(hours_min: str, zone: str):
def main():


Question: import json
import os
import discord
from discord.ext import commands
from discord.ext.commands import BadArgument
from clembot.core.bot import group, command
from clembot.core.errors import wrap_error
from clembot.exts.bingo.bingo_card_manager import BingoCardManager
from clembot.exts.bingo.bingo_card_writer import BingoCardWriter
from clembot.exts.bingo.bingo_data_generator import BingoDataGenerator
from clembot.exts.config.globalconfigmanager import GlobalConfigCache
from clembot.exts.config.guild_metadata import GuildMetadata
from clembot.utilities.timezone import timehandler as TH
from clembot.utilities.utils.utilities import Utilities




class BingoCog(commands.Cog):

    def __init__(self, bot):
        self.bot = bot
        self.dbi = bot.dbi
        self.utilities = Utilities()

Answer:         self.MyBingoCardManager = BingoCardManager(bot.dbi)