clembot/utilities/utils/embeds.py
class Embeds:

    def __init__(self):
        return

    @staticmethod
    async def ask_for_input(ctx, prompt_embed, validate_response=None):
        """
        returns cancel if configuration is cancelled.
        """
        def check(msg):
            return msg.author == ctx.message.author and msg.channel == ctx.channel

        await ctx.send(embed=prompt_embed)

        while True:
            try:
                response = await ctx.bot.wait_for('message', check=check, timeout=60)
            except asyncio.TimeoutError:
                await Embeds.error(ctx.channel, 'Too late!')
                return "cancel"
            except Exception as error:
                break
            response_content = response.content.strip()
            if response_content == "cancel" or (validate_response and validate_response(response_content)):
                return response_content
            else:
                await Embeds.error(ctx.channel, 'I could not interpret your response. Try again!')
                continue
        return "cancel"


    @staticmethod
    def google_location_preview_url(lat_long):
        key = config_template.api_keys["google-api-key"]
        gmap_base_url = f"https://maps.googleapis.com/maps/api/staticmap?center={lat_long}&markers=color:blue%7C{lat_long}&maptype=roadmap&size=250x125&zoom=15&key={key}"

        return gmap_base_url

    @staticmethod
    async def message(channel, description, title=None, footer=None, user=None, icon=MyEmojis.INFO):
        try:
            error_message = "The output contains more than 2000 characters."

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

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

            message_embed = discord.Embed(description=f"{user_mention}{description}", colour=discord.Colour.green(), 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 error(channel, description, user=None):

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

    @staticmethod
    async def error_notification(ctx, header, error, disappear_in=30):
        """The message stays for 30 seconds and disappears!"""
        try:
            err_msg = await ctx.send(embed=Embeds.make_embed(msg_type='error', header=header, content=error.__str__()))
            if disappear_in:
                await asyncio.sleep(disappear_in)
                await err_msg.delete()
        except Exception as err:
            Logger.error(err)


    @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 more1.**" if len(text) > length else text
        return text

    @staticmethod
    def make_embed(msg_type='', header=None, header_icon=None, title=None, title_url=None, content=None, thumbnail='',
                   image='', fields=None, footer=None, footer_icon=None, inline=True, guild=None, msg_color=None):
        """Returns a formatted discord embed object.

        Define either a type or a colour.
        Types are:
        error, warning, info, success, help.
        """
        embed_types = {
            'error':{
                'icon':'https://i.imgur.com/juhq2uJ.png',
                'colour':'red'
            },
            'warning':{
                'icon':'https://i.imgur.com/4JuaNt9.png',
                'colour':'gold'
            },
            'info':{
                'icon':'https://i.imgur.com/wzryVaS.png',
                'colour':'blue'
            },
            'success':{
                'icon':'https://i.imgur.com/ZTKc3mr.png',
                'colour':'green'
            },
            'help':{
                'icon':'https://i.imgur.com/kTTIZzR.png',
                'colour':'blue'
            }
        }
        if msg_type in embed_types.keys():
            msg_color = embed_types[msg_type]['colour']
            header_icon = embed_types[msg_type]['icon']
        if guild and not msg_color:
            msg_color = color(guild)
        else:
            if not isinstance(msg_color, discord.Colour):
                msg_color = color(msg_color)

        embed = discord.Embed(title=title or discord.Embed.Empty, url=title_url or discord.Embed.Empty,
            description=content, colour=msg_color)

        if header:
            embed.set_author(name=header, icon_url=header_icon or discord.Embed.Empty, url=discord.Embed.Empty)

        if thumbnail:
            embed.set_thumbnail(url=thumbnail)
        if image:
            embed.set_image(url=image)
        if fields:
            for key, value in fields.items():
                ilf = inline
                if not isinstance(value, str):
                    if not value:
                        continue
                    else:
                        ilf = value[0]
                        value = value[1]
                if value:
                    embed.add_field(name=f"**{key}**", value=Embeds.trim_to(value, 995), inline=ilf)
        if footer:
            footer = {'text':footer}
            if footer_icon:
                footer['icon_url'] = footer_icon
            embed.set_footer(**footer)
        return 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/core/checks.py
def is_guild_mod():
    return commands.check(check_is_moderator)
clembot/core/checks.py
def is_guild_admin():
    return commands.check(check_is_guild_admin)
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/pkmn/spelling.py
class SpellHelper:

    words = []
    n = None


    @classmethod
    def set_dictionary(cls, word_list):
        cls.words = Counter(word_list)
        cls.n = sum(cls.words.values())


    @classmethod
    def all_words(cls, text):
        return re.findall(r'\w+', text.upper())


    @classmethod
    def P(cls, word):
        "Probability of `word`."
        if not cls.n:
            return 0

        return cls, cls.words[word] / cls.n


    @classmethod
    def correction(cls, word):
        "Most probable spelling correction for word."
        return max(cls.candidates(word), key=cls.P)


    @classmethod
    def candidates(cls, word):
        "Generate possible spelling corrections for word."
        return cls.known([word]) or cls.known(cls.edits1(word)) or cls.known(cls.edits2(word)) or [word]


    @classmethod
    def known(cls, words):
        "The subset of `words` that appear in the dictionary of WORDS."
        if not cls.words:
            return None

        return set(w.upper() for w in words if w.upper() in cls.words)

    @classmethod
    def edits1(cls, word):
        "All edits that are one edit away from `word`."
        letters    = 'abcdefghijklmnopqrstuvwxyz'
        splits     = [(word[:i], word[i:])    for i in range(len(word) + 1)]
        deletes    = [L + R[1:]               for L, R in splits if R]
        transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R)>1]
        replaces   = [L + c + R[1:]           for L, R in splits if R for c in letters]
        inserts    = [L + c + R               for L, R in splits for c in letters]
        return set(deletes + transposes + replaces + inserts)


    @classmethod
    def edits2(cls, word):
        "All edits that are two edits away from `word`."
        return (e2 for e1 in cls.edits1(word) for e2 in cls.edits1(e1))
clembot/config/constants.py
class MyEmojis:

    DESPAWNED = '💨'
    ON_MY_WAY = '🏎️'
    TRASH = '🗑️'

    REMOTE = f"{parse_emoji(None, config_template.misc_emoji.get('remote_raid'))}"
    INVITE = f"{parse_emoji(None, config_template.misc_emoji.get('add_friend'))}"
    HERE = f"{parse_emoji(None, config_template.misc_emoji.get('here'))}"
    COMING = f"{parse_emoji(None, config_template.misc_emoji.get('coming'))}"
    INTERESTED = f"{parse_emoji(None, config_template.misc_emoji.get('interested'))}"
    INFO = f"{parse_emoji(None, config_template.misc_emoji.get('info'))}"
    ERROR = f"{parse_emoji(None, config_template.misc_emoji.get('error'))}"
    POKE_BATTLER = f"{parse_emoji(None, config_template.misc_emoji.get('pb'))}"
clembot/core/checks.py
class AccessDenied(CommandError):
async def _check_is_owner(ctx):
async def _check_is_trusted(ctx):
async def _check_is_guild_owner(ctx):
async def _check_is_guild_admin(ctx):
async def _check_is_moderator(ctx):
async def check_is_owner(ctx):
async def check_is_trusted(ctx):
async def check_is_guild_owner(ctx):
async def check_is_guild_admin(ctx):
async def check_is_moderator(ctx):
def go_thru_sometimes():
def is_bot_owner():
def is_trusted():
def is_guild_owner():
def is_guild_admin():
def is_guild_mod():
clembot/config/constants.py
GUILD_METADATA_KEY = ["hide-nest-preview", "bingo-card-repo", "bingo-event-title", "bingo-event-pokemon", "notifications"]
clembot/config/constants.py
GUILD_CONFIG_KEY = ['prefix', 'city', 'timezone', 'welcome', 'teams', 'config']
clembot/config/constants.py
CHANNEL_METADATA_KEY = ["city"]
clembot/core/logs.py
def init_loggers():
clembot/exts/config/channel_metadata.py
class ChannelMetadata:

    by_channel = dict()
    _in_progress_config_channels = []

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

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

    @classmethod
    def begin_configuration(cls, channel_id):
        cls._in_progress_config_channels.append(channel_id)


    @classmethod
    def end_configuration(cls, channel_id):
        if cls.config_in_progress(channel_id):
            cls._in_progress_config_channels.remove(channel_id)

    @classmethod
    def config_in_progress(cls, channel_id):
        return channel_id in cls._in_progress_config_channels


    @property
    def _data(self):
        report_channel_query = self.bot.dbi.table('channel_metadata').query()
        _data = report_channel_query.where(channel_id=self.channel.id)
        return _data

    @classmethod
    def cache(cls, channel_dict):
        ChannelMetadata.by_channel[channel_dict.get('channel_id')] = channel_dict


    @classmethod
    def evict(cls, channel_id):
        ChannelMetadata.by_channel.pop(channel_id, None)


    @classmethod
    async def find(cls, bot, channel_id, guild_id = None):

        channel_metadata = ChannelMetadata.by_channel.get(channel_id)
        if channel_metadata:
            return channel_metadata

        report_channel_query = bot.dbi.table('channel_metadata').query()
        _data = report_channel_query.where(channel_id=channel_id)
        db_record = await _data.get()

        if db_record:
            channel_metadata = ChannelMetadata.deserialize(dict(db_record[0]))
            ChannelMetadata.cache(channel_metadata)
            return channel_metadata

        if guild_id:
            await ChannelMetadata.insert(bot, {'channel_id': channel_id, 'guild_id': guild_id})
        return ChannelMetadata.by_channel.get(channel_id)


    @classmethod
    async def city(cls, bot, channel_id):
        channel_dict = await ChannelMetadata.find(bot, channel_id)
        if channel_dict:
            return channel_dict.get('city')
        raise Exception("City has not been set for this channel.")

    @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, channel_dict):
        channel_metadata_table = bot.dbi.table('channel_metadata')
        update_dict=ChannelMetadata.serialize(channel_dict)
        channel_metadata_table_update = channel_metadata_table.update(**update_dict).where(channel_id=channel_dict.get('channel_id'))
        await channel_metadata_table_update.commit()
        ChannelMetadata.cache(channel_dict)

    @classmethod
    async def insert(cls, bot, channel_dict):
        channel_metadata_table = bot.dbi.table('channel_metadata')
        insert_dict = ChannelMetadata.serialize(channel_dict)
        channel_metadata_table_insert = channel_metadata_table.insert(**insert_dict)
        await channel_metadata_table_insert.commit()
        ChannelMetadata.cache(channel_dict)

    @staticmethod
    def embed(ctx, channel_dict):
        return (ChannelConfigEmbed.from_channel_metadata(ctx, channel_dict)).embed

    @staticmethod
    def success_embed(ctx, channel_dict):
        embed = (ChannelConfigEmbed.from_channel_metadata(ctx, channel_dict, "Configuration has been updated successfully.", Icons.configure_success)).embed
        return embed

    @staticmethod
    def failure_embed(ctx, channel_dict):
        embed = (ChannelConfigEmbed.from_channel_metadata(ctx, channel_dict, "No configuration changes done.", Icons.configure_failure)).embed
        return embed

    @staticmethod
    def profile_embed(ctx, channel_dict, title=None):
        return (ChannelConfigEmbed.from_channel_profile(ctx, channel_dict, title)).embed
clembot/config/constants.py
class Icons:

    raid_report = "https://i.imgur.com/uRhgISs.png"
    configure = "https://i.imgur.com/nPyXbkD.png"
    configure_success = "https://i.imgur.com/OBlddqw.png"
    configure_failure = "https://i.imgur.com/30rAjXD.png"
    uptime = "https://i.imgur.com/82Cqf1x.png"
    wild_report = "https://i.imgur.com/eW8sCSo.png"
    field_research = "https://raw.githubusercontent.com/TrainingB/Clembot/v1-rewrite/images/field-research.png?cache=13"
    research_report = "https://i.imgur.com/O1XNv5z.png"
    BOT_ERROR= "https://i.imgur.com/C3qZaeo.png"
    trash = "https://i.imgur.com/K6iLiPP.png"
    error = "https://i.imgur.com/dfyevnZ.png"

    INVALID_INPUT = "https://i.imgur.com/Sl9Nr3g.png"
    BOT_ERROR_2 = "https://i.imgur.com/P8UEhkD.png"
    CONFIGURATION = "https://i.imgur.com/Brzu64u.png"
    INVALID_ACCESS = "https://i.imgur.com/8LOsBQS.png"
    TIMEZONE="https://i.imgur.com/j5L85oY.png"


    @staticmethod
    def avatar(user: discord.Member):
        icon_url = f"https://cdn.discordapp.com/avatars/{user.id}/{user.avatar}.jpg?size=32"
        return icon_url
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/config/constants.py
GLOBAL_CONFIG_KEY = ["bingo-event-title", "cache-version", "timezone", "bingo-event-pokemon", "next-badge-id", "game-master-version"]
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()}")


Question: import json
import discord
import pytz
from discord.ext import commands
from discord.ext.commands import BadArgument
from clembot.config.constants import Icons, MyEmojis, GUILD_METADATA_KEY, GUILD_CONFIG_KEY, GLOBAL_CONFIG_KEY, \
    CHANNEL_METADATA_KEY
from clembot.core import checks
from clembot.core.bot import group
from clembot.core.checks import is_guild_admin, is_guild_mod
from clembot.core.errors import wrap_error
from clembot.core.logs import Logger
from clembot.exts.config.channel_metadata import ChannelMetadata
from clembot.exts.config.globalconfigmanager import GlobalConfigCache
from clembot.exts.pkmn.spelling import SpellHelper
from clembot.utilities.utils.embeds import Embeds
from clembot.utilities.utils.utilities import Utilities
            country_name = f" ({pytz.country_names[country_code]})"
            timezones = '\n'.join([tz for tz in timezone_list if tz in pytz.common_timezones])
        except KeyError:
            timezone_list = []
            country_name = ""
            timezones = 'No timezone found. Are you sure you provided a valid ISO 3166 country code?'

        response_message = await ctx.send(embed=Embeds.make_embed(header=f"Available Timezones for {country_code}{country_name}",
                                               header_icon=Icons.TIMEZONE,
                                               msg_color=discord.Color.blue(),
                                               content=f"{timezones}",
                                                footer="Timezone info from Olson database"))

        await response_message.add_reaction(MyEmojis.TRASH)


    @group(pass_context=True, category='Bot Info', aliases=["config"])
    @is_guild_admin()
    async def cmd_config(self, ctx):
        """
        Command to read / update the configuration for global, guild or channel. A few shortcuts are provided for city & timezone as well.
        `!config guild`
        `!config channel`
        `!config timezone America/Los_Angeles` - set timezone for guild
        `!config city  BURBANKCA` - set city for guild
        """
        if ctx.invoked_subcommand is None:
            if ctx.subcommand_passed is None:
                return await self.cmd_config_guild(ctx)

            raise BadArgument("`!config` can be used with `guild, channel, timezone, city`")


    @cmd_config.command(pass_context=True, category='Bot Info', aliases=["timezone"])
    @wrap_error
    @is_guild_admin()
    async def cmd_config_timezone(self, ctx, timezone):

        if timezone in pytz.all_timezones:
            await ctx.guild_profile(key='timezone', value=timezone)
            config = await ctx.guild_profile(key='timezone')
            return await Embeds.message(ctx.message.channel, f"**timezone** is set to **{config}**")

        raise BadArgument(f"**{timezone}** didn't resolve to a valid timezone. \nYou can see a list for a valid timezone for a country ISO 3166 country code using `!timezone country_code`")


    @cmd_config.command(pass_context=True, category='Bot Info', aliases=["city"])
    @wrap_error
    @is_guild_admin()
    async def cmd_config_city(self, ctx, *city):
        if city:
            city_state = ''.join(city).upper()
            await ctx.guild_profile(key='city', value=city_state)
            config = await ctx.guild_profile(key='city')
            return await Embeds.message(ctx.message.channel, f"**city** is set to **{config}**")
        else:
            raise BadArgument("Please specify city and state, in all caps, without spaces.")


    @cmd_config.command(pass_context=True, category='Bot Info', aliases=["guild"])
    @wrap_error
    @is_guild_admin()
    async def cmd_config_guild(self, ctx, config_name=None, config_value=None):
        """
        View/Change guild configuration.
        **Example**
        `!config guild city BURBANKCA`
        """
        if config_name and config_name not in GUILD_CONFIG_KEY and config_name not in GUILD_METADATA_KEY:
            return await Embeds.error(ctx.message.channel, "No such configuration exists.")

        config = await ctx.guild_profile(key=config_name, value=config_value)
        if config_name:
            if config_value:
                config = await ctx.guild_profile(key=config_name)
            else:
                config = await ctx.guild_profile(key=config_name, delete=True)
            await Embeds.message(ctx.message.channel, f"**{config_name}** is set to **{config}**")
        else:
            await ConfigCog.send_guild_config_embed(ctx, config)


    @cmd_config.command(pass_context=True, category='Bot Info', aliases=["channel"])
    @wrap_error
    @is_guild_mod()
    async def cmd_config_channel(self, ctx, config_name=None, config_value=None):
        """
        View/Change channel configuration.
        **Example**
        `!config channel city BURBANKCA`
        """
        if config_name and config_name not in CHANNEL_METADATA_KEY:
            return await Embeds.error(ctx.message.channel, "No such configuration exists.")

        if config_name:
            if config_value:
                await ctx.channel_profile(channel_id=ctx.message.channel.id, key=config_name)
            else:
                await ctx.channel_profile(channel_id=ctx.message.channel.id, key=config_name, delete=True)

        await ctx.channel_profile(channel_id=ctx.message.channel.id, key=config_name, value=config_value)

        config = await ctx.channel_profile(channel_id=ctx.message.channel.id)
        await ctx.send(embed = ChannelMetadata.profile_embed(ctx, config))


    @staticmethod
    async def send_guild_config_embed(ctx, config):

        embed = Embeds.make_embed(header="Guild Configuration", header_icon=Icons.CONFIGURATION,
                        fields={ key.capitalize():value for key, value in config.items()}, msg_color=discord.Color.blue(),
                        inline=True)

        return await ctx.send(embed=embed)


    @staticmethod
    async def send_global_config_embed(ctx, config):

        embed = Embeds.make_embed(header="Clembot Configuration", header_icon=Icons.CONFIGURATION,

Answer:                         fields={k[0]:k[1] for k in config.items() if k[0] in GLOBAL_CONFIG_KEY and k[1] is not None or ''},