コード例 #1
0
ファイル: settings.py プロジェクト: haliphax/aethersprite
    async def set(self, ctx, name: str, *, value: str,
                  channel: Optional[str] = None):
        """
        Change/view a setting's value

        If [name] is not provided, a list of all settings (but not their values) will be shown. If [value] is not provided, the setting's current value (and its default) will be shown, instead. Note that channel-dependent settings must be set and viewed in relevant channels.

        Use the 'channel' parameter to specify a channel other than the current one.

        Example: set key value channel=#lobby
        """

        chan = ctx.channel
        matches = re.findall(r'\bchannel=(.+?)$', value)
        val = value

        if len(matches) > 0:
            chan = await chan_converter.convert(ctx, matches[0])
            val = re.sub(r'\bchannel=.+?$', '', value).strip()

        if name not in settings:
            await ctx.send(MSG_NO_SETTING)
            log.warn(f'{ctx.author} attempted to set nonexistent setting: '
                     f'{name} in {chan}')

            return

        if settings[name].set(ctx, val, channel=chan):
            await ctx.send(f':thumbsup: Value updated.')
            log.info(f'{ctx.author} updated setting {name}: {val} in {chan}')
        else:
            await ctx.send(f':thumbsdown: Error updating value.')
            log.warn(f'{ctx.author} failed to update setting {name}: {val} '
                     f'in {chan}')
コード例 #2
0
    async def reset(self, ctx, *,
                    params: channel_arg = channel_arg.defaults()):
        """
        Reset Only whitelist

        Using this command will disable whitelisting behavior and remove the existing whitelist.

        Use the 'channel' parameter to specify a channel other than the current one.

        Example: only.reset channel=#lobby
        """

        channel = params['channel'] if 'channel' in params else ctx.channel
        chan_id = str(channel.id)
        guild = str(ctx.guild.id)
        ours = onlies[guild] if guild in onlies else None
        ourchan = ours[chan_id] \
                if ours is not None and chan_id in ours else None

        if ourchan is None:
            await ctx.send(':person_shrugging: None set.')

            return

        del ours[chan_id]

        if len(ours) == 0:
            del onlies[guild]
        else:
            onlies[guild] = ours

        await ctx.send(':boom: Reset.')
        log.info(f'{ctx.author} reset Only whitelist for {channel}')
コード例 #3
0
ファイル: roles.py プロジェクト: haliphax/aethersprite
async def on_raw_reaction_remove(payload: RawReactionActionEvent):
    "Handle on_reaction_remove event."

    if payload.user_id == bot.user.id:
        return

    directory = directories[payload.guild_id] \
        if payload.guild_id in directories else None

    if payload.message_id not in posts and (
            directory is None or payload.message_id != directory['message']):
        return

    split = str(payload.emoji).split('\ufe0f')

    if len(split) != 2:
        return

    guild: Guild = bot.get_guild(payload.guild_id)
    member: Member = guild.get_member(payload.user_id)
    fake_ctx = FakeContext(guild=guild)
    setting = settings['roles.catalog'].get(fake_ctx, raw=True)
    roles = sorted([r for r in guild.roles if r.id in setting],
                   key=lambda x: x.name.lower())
    which = int(split[0])

    if which < 0 or which > len(roles):
        return

    role = roles[which]
    await member.remove_roles(role)
    log.info(f'{member} removed role {role}')
コード例 #4
0
ファイル: roles.py プロジェクト: haliphax/aethersprite
async def catalog(ctx: Context):
    "Create a permanent roles catalog post in the current channel."

    roles = settings['roles.catalog'].get(ctx)

    if roles is None or len(roles) == 0:
        await ctx.send(':person_shrugging: There are no available '
                       'self-service roles.')
        log.warn(f'{ctx.author} attempted to post roles catalog, but no roles '
                 'are available')

        return

    guild_id = str(ctx.guild.id)

    if guild_id in directories:
        existing = directories[guild_id]
        chan: TextChannel = ctx.guild.get_channel(existing['channel'])

        if chan is not None:
            try:
                msg = await chan.fetch_message(existing['message'])
                await msg.delete()
            except NotFound:
                pass

    msg = await _get_message(ctx)
    directories[guild_id] = {'message': msg.id,
                             'channel': ctx.channel.id}

    log.info(f'{ctx.author} posted roles catalog to {ctx.channel}')
    await ctx.message.delete()
コード例 #5
0
ファイル: poll.py プロジェクト: haliphax/aethersprite
async def _update_poll(member: Member, message: Message, emoji: str,
                       adjustment: int):
    poll = polls[message.id]
    opts = poll['options']
    opt = opts[emoji]
    opt['count'] += adjustment
    acted = False

    if adjustment > 0:
        if member.id in opt['votes']:
            # correct count if they voted but reacts are out of sync
            opt['count'] -= adjustment
        else:
            acted = True
            opt['votes'].add(member.id)

    elif adjustment < 0 and member.id in opt['votes']:
        acted = True
        opt['votes'].remove(member.id)

    opts[emoji] = opt
    poll['options'] = opts
    polls[message.id] = poll
    verb = 'voted' if adjustment > 0 else 'retracted vote'

    if acted:
        log.info(f'{member} {verb} for {emoji} - {poll["prompt"]}')
    else:
        log.warn(f'Ignored vote for {emoji} by {member} in {message.id} - '
                 f'{poll["prompt"]} (reacts are out of sync)')

    await message.edit(embed=_get_embed(poll))
コード例 #6
0
    async def list(self,
                   ctx,
                   server: typing.Optional[bool] = False,
                   *,
                   params: channel_arg = channel_arg.defaults()):
        """
        List all current channel's whitelisted commands

        Use the 'channel' parameter to specify a channel other than the current one.

        Example: only.list channel=#lobby"
        """

        channel = params['channel'] if 'channel' in params else ctx.channel
        chan_id = str(channel.id)
        guild = str(ctx.guild.id)

        if guild not in onlies:
            onlies[guild] = dict()

        ours = onlies[guild]

        if channel not in ours:
            ours[channel] = set([])

        output = '**, **'.join(ours[chan_id])

        if not len(output):
            output = 'None'

        log.info(f'{ctx.author} viewed command whitelist for {channel}')
        await ctx.send(f':guard: **{output}**')
コード例 #7
0
ファイル: lobotomy.py プロジェクト: Argavyon/aethersprite
    async def add(self, ctx, command, server: typing.Optional[bool] = False):
        """
        Disable the given command

        Disables <command> in this channel. If [server] is True, then the command is disabled on the entire server.
        """

        server_key = command.lower().strip()
        key = f'{server_key}#{ctx.channel.id}'
        guild = str(ctx.guild.id)

        if not ctx.guild.id in lobotomies:
            lobotomies[guild] = set([])

        lobs = lobotomies[guild]

        if (key in lobs and not server) \
                or (server_key in lobs and server):
            await ctx.send(f':newspaper: Already done.')

            return

        # if it's already in the opposite category (channel vs. server),
        # then clear it out
        if key in lobs and server:
            lobs.remove(key)
        elif server_key in lobs and not server:
            lobs.remove(server_key)

        lobs.add(server_key if server else key)
        lobotomies[guild] = lobs
        log.info(f'{ctx.author} lobotomized {server_key if server else key}')
        await ctx.send(f':brain: Done.')
コード例 #8
0
ファイル: lobotomy.py プロジェクト: Argavyon/aethersprite
    async def remove(self, ctx, command, server: typing.Optional[bool] = False):
        """
        Enable the given command

        Enables <command> in this channel. If [server] is True, then the command is enabled on the entire server.
        """

        server_key = command.lower().strip()
        key = f'{server_key}#{ctx.channel.id}'
        guild = str(ctx.guild.id)
        lobs = lobotomies[guild] if guild in lobotomies else None

        if lobs is None:
            await ctx.send(':person_shrugging: None set.')

            return

        if (key in lobs and server) or (server_key in lobs and not server):
            await ctx.send(':thumbsdown: The opposite scope is '
                           'currently set.')

            return

        lobs.remove(server_key if server else key)
        lobotomies[guild] = lobs
        log.info(f'{ctx.author} removed {server_key if server else key}')
        await ctx.send(':wastebasket: Removed.')
コード例 #9
0
ファイル: only.py プロジェクト: Argavyon/aethersprite
    async def reset(self, ctx):
        """
        Reset Only whitelist

        Using this command will disable whitelisting behavior and remove the existing whitelist.
        """

        guild = str(ctx.guild.id)
        channel = str(ctx.channel.id)
        ours = onlies[guild] if guild in onlies else None
        ourchan = ours[channel] \
                if ours is not None and channel in ours else None

        if ourchan is None:
            await ctx.send(':person_shrugging: None set.')

            return

        del ours[channel]

        if len(ours) == 0:
            del onlies[guild]
        else:
            onlies[guild] = ours

        await ctx.send(':boom: Reset.')
        log.info(f'{ctx.author} reset Only whitelist for {ctx.channel}')
コード例 #10
0
ファイル: only.py プロジェクト: Argavyon/aethersprite
    async def remove(self, ctx, command):
        """
        Remove the given command from the Only whitelist

        If whitelisting is enabled for this channel, the removed command can no longer be executed.
        """

        guild = str(ctx.guild.id)
        channel = str(ctx.channel.id)
        ours = onlies[guild] if guild in onlies else None
        ourchan = ours[channel] \
                if ours is not None and channel in ours else None

        if ourchan is None:
            await ctx.send(':person_shrugging: None set.')

            return

        ourchan.remove(command)

        if len(ourchan) == 0:
            del ours[channel]
        else:
            ours[channel] = ourchan

        if len(ours) == 0:
            del onlies[guild]
        else:
            onlies[guild] = ours

        log.info(f'{ctx.author} removed {command} from {ctx.channel} '
                 'whitelist')
        await ctx.send(':wastebasket: Removed.')
コード例 #11
0
ファイル: only.py プロジェクト: Argavyon/aethersprite
    async def add(self, ctx, command):
        """
        Add the given command to the Only whitelist

        Enables <command> in this channel.
        """

        guild = str(ctx.guild.id)
        channel = str(ctx.channel.id)

        if guild not in onlies:
            onlies[guild] = dict()

        ours = onlies[guild]

        if channel not in ours:
            ours[channel] = set([])

        ourchan = ours[channel]

        if command in ourchan:
            await ctx.send(f':newspaper: Already done.')

            return

        ourchan.add(command)
        ours[channel] = ourchan
        onlies[guild] = ours
        log.info(f'{ctx.author} added {command} to {ctx.channel} whitelist')
        await ctx.send(f':shield: Done.')
コード例 #12
0
ファイル: roles.py プロジェクト: Argavyon/aethersprite
async def on_raw_reaction_add(payload: RawReactionActionEvent):
    "Handle on_reaction_add event."

    if payload.user_id == bot.user.id or payload.message_id not in posts:
        return

    guild: Guild = bot.get_guild(payload.guild_id)
    channel: TextChannel = guild.get_channel(payload.channel_id)
    message: Message = await channel.fetch_message(payload.message_id)
    member: Member = guild.get_member(payload.user_id)
    split = str(payload.emoji).split('\ufe0f')

    if len(split) != 2:
        await message.remove_reaction(payload.emoji, member)

        return

    fake_ctx = FakeContext(guild=guild)
    setting = settings['roles.catalog'].get(fake_ctx, raw=True)
    roles = sorted([r for r in guild.roles if r.id in setting],
                   key=lambda x: x.name.lower())
    which = int(split[0])

    if which < 0 or which > len(roles):
        await message.remove_reaction(payload.emoji, member)

        return

    role = roles[which]
    await member.add_roles(role)
    log.info(f'{member} added role {role}')
コード例 #13
0
ファイル: alias.py プロジェクト: haliphax/aethersprite
    async def add(self, ctx, alias, command):
        "Add an alias of <alias> for <command>"

        guild = str(ctx.guild.id)

        if ctx.guild.id not in aliases:
            aliases[guild] = dict()

        als = aliases[guild]

        if alias in als:
            await ctx.send(f':newspaper: Already exists.')

            return

        cmd = ctx.bot.get_command(command)

        if cmd is None:
            await ctx.send(f':scream: No such command!')

            return

        als[alias] = command
        aliases[guild] = als
        log.info(f'{ctx.author} added alias {alias} for {command}')
        await ctx.send(f':sunglasses: Done.')
コード例 #14
0
ファイル: poll.py プロジェクト: haliphax/aethersprite
    async def _delete():
        prompt = poll['prompt']
        delete = payload.member.id in poll['delete']
        confirm = payload.member.id in poll['confirm']

        if delete and confirm:
            await msg.delete()
            del polls[msg.id]
            log.info(f'{payload.member} deleted poll {msg.id} - {prompt}')
コード例 #15
0
ファイル: github.py プロジェクト: haliphax/aethersprite
async def github(ctx):
    """
    This bot is running on Aethersprite, an open source bot software built with discord.py. My source code is available for free. Contributions in the form of code, bug reports, and feature requests are all welcome.

    https://github.com/haliphax/aethersprite
    """

    await ctx.send('For source code, feature requests, and bug reports, visit '
                   'https://github.com/haliphax/aethersprite')
    log.info(f'{ctx.author} requested GitHub URL')
コード例 #16
0
ファイル: poll.py プロジェクト: haliphax/aethersprite
async def poll(ctx: Context, *, options: str):
    """
    Create a poll

    To create a poll, options must be provided. Separate options with commas. You may provide a prompt if you wish by encasing the first argument to the command in brackets.

    To delete a poll, use both the Delete and Confirm reactions. Only a moderator, administrator, or the creator of the poll may delete it.

    Examples:
        !poll The dress is green, The dress is gold
        !poll [Do you see what I see?] Yes, No
    """

    match = re.match(r'^(?:\[([^\]]+)\]\s*)?(.+)$', options)

    if match is None:
        await ctx.message.add_reaction(THUMBS_DOWN)
        log.warn(f'{ctx.author} Provided invalid arguments: {options}')

        return

    prompt, qstr = match.groups()
    count = 1
    opts = {}

    for s in qstr.split(','):
        emoji = f'{count}{DIGIT_SUFFIX}'
        opt = s.strip()
        opts[emoji] = {'text': opt, 'count': 0, 'votes': set([])}
        count += 1

    poll = {
        'timestamp': datetime.utcnow(),
        'author': ctx.author.display_name,
        'author_id': ctx.author.id,
        'avatar': str(ctx.author.avatar_url),
        'prompt': prompt,
        'options': opts,
        'open': 1,
        'delete': set([]),
        'confirm': set([])
    }
    msg: Message = await ctx.send(embed=_get_embed(poll))

    for emoji in opts.keys():
        await msg.add_reaction(emoji)

    await msg.add_reaction(PROHIBITED)
    await msg.add_reaction(WASTEBASKET)
    await msg.add_reaction(CHECK_MARK)

    polls[msg.id] = poll
    log.info(f'{ctx.author} created poll: {poll!r}')
    await ctx.message.delete()
コード例 #17
0
    async def clear(self, ctx, name):
        "Reset setting <name> to its default value"

        if name not in settings:
            log.warn(f'{ctx.author} attempted to clear nonexistent setting: '
                     f'{name}')
            await ctx.send(MSG_NO_SETTING)

            return

        settings[name].set(ctx, None, raw=True)
        await ctx.send(':negative_squared_cross_mark: Setting cleared.')
        log.info(f'{ctx.author} cleared setting {name}')
コード例 #18
0
async def on_member_join(member: Member):
    "Greet members when they join."

    chan_setting = settings['greet.channel'].get(member)
    msg_setting = settings['greet.message'].get(member)

    if chan_setting is None or msg_setting is None:
        return

    channel = [c for c in member.guild.channels if c.name == chan_setting][0]
    log.info(f'Greeting new member {member} in {member.guild.name} '
             f'#{channel.name}')
    await channel.send(msg_setting.format(name=member.display_name, nl='\n'))
コード例 #19
0
ファイル: alias.py プロジェクト: haliphax/aethersprite
    async def list(self, ctx):
        "List all command aliases"

        guild = str(ctx.guild.id)

        if guild not in aliases:
            aliases[guild] = dict()

        als = aliases[guild]
        output = ', '.join([f'`{k}` => `{als[k]}`' for k in als.keys()])

        if len(output) == 0:
            output = 'None'

        log.info(f'{ctx.author} viewed alias list')
        await ctx.send(f':detective: **{output}**')
コード例 #20
0
ファイル: gmt.py プロジェクト: haliphax/aethersprite
async def gmt(ctx, *, offset: typing.Optional[str]):
    """
    Get current time in GMT or offset by days, hours, and minutes.

    To get the current time, no arguments are necessary. To get offset time (e.g. 5 hours from now), provide values for days, hours, or minutes. For offsets in the past, use negative numbers.

    Example: !gmt 3d 6h 17m  <-- would request an offset of 3 days, 6 hours, 17 minutes

    Arguments aren't validated, so anything goes... but please be reasonable. The command will silently fail if you choose an offset the bot can't process.
    """

    delta = get_timespan_chunks(offset) if offset else (0, 0, 0)
    days, hours, minutes = delta
    thetime = (datetime.now(timezone.utc) +
               timedelta(days=days, hours=hours, minutes=minutes))
    offset_str = thetime.strftime(DATETIME_FORMAT)
    await ctx.send(f':clock: {offset_str}')
    log.info(f'{ctx.author} requested GMT offset of {delta}: {offset_str}')
コード例 #21
0
ファイル: roles.py プロジェクト: haliphax/aethersprite
def _delete(id: int):
    if id not in posts:
        return

    post = posts[id]
    guild: Guild = bot.get_guild(post['guild'])
    channel: TextChannel = guild.get_channel(post['channel'])

    async def f():
        try:
            msg: Message = await channel.fetch_message(id)
            await msg.delete()
        except NotFound:
            pass

        del posts[id]

    aio.ensure_future(f())
    log.info(f'Deleted roles self-service post {id}')
コード例 #22
0
ファイル: alias.py プロジェクト: haliphax/aethersprite
    async def remove(self, ctx, alias):
        "Remove <alias>"

        guild = str(ctx.guild.id)
        als = aliases[guild] if guild in aliases else None

        if als is None or alias not in als:
            await ctx.send(':person_shrugging: None set.')

            return

        del als[alias]

        if len(als) == 0:
            del aliases[guild]
        else:
            aliases[guild] = als

        log.info(f'{ctx.author} removed alias {alias}')
        await ctx.send(':wastebasket: Removed.')
コード例 #23
0
ファイル: settings.py プロジェクト: haliphax/aethersprite
    async def desc(self, ctx, name):
        "View description of setting <name>"

        if name not in settings:
            await ctx.send(MSG_NO_SETTING)
            log.warn(f'{ctx.author} attempted to view description of '
                     f'nonexistent setting {name}')

            return

        setting = settings[name]

        if setting.description is None:
            await ctx.send(':person_shrugging: No description set.')
        else:
            await ctx.send(f':book: `{setting.name}` '
                           f'_(Channel: **{str(setting.channel)}**)_\n'
                           f'> {setting.description}')

        log.info(f'{ctx.author} viewed description of setting {name}')
コード例 #24
0
    async def remove(self,
                     ctx,
                     command,
                     *,
                     params: channel_arg = channel_arg.defaults()):
        """
        Remove the given command from the Only whitelist

        If whitelisting is enabled for this channel, the removed command can no longer be executed.

        Use the 'channel' parameter to specify a channel other than the current one.

        Example: only.remove test channel=#lobby
        """

        channel = params['channel'] if 'channel' in params else ctx.channel
        chan_id = str(channel.id)
        guild = str(ctx.guild.id)
        ours = onlies[guild] if guild in onlies else None
        ourchan = ours[chan_id] \
                if ours is not None and chan_id in ours else None

        if ourchan is None:
            await ctx.send(':person_shrugging: None set.')

            return

        ourchan.remove(command)

        if len(ourchan) == 0:
            del ours[chan_id]
        else:
            ours[chan_id] = ourchan

        if len(ours) == 0:
            del onlies[guild]
        else:
            onlies[guild] = ours

        log.info(f'{ctx.author} removed {command} from {channel} whitelist')
        await ctx.send(':wastebasket: Removed.')
コード例 #25
0
ファイル: only.py プロジェクト: Argavyon/aethersprite
    async def list(self, ctx, server: typing.Optional[bool] = False):
        "List all current channel's whitelisted commands"

        guild = str(ctx.guild.id)
        channel = str(ctx.channel.id)

        if guild not in onlies:
            onlies[guild] = dict()

        ours = onlies[guild]

        if channel not in ours:
            ours[channel] = set([])

        output = '**, **'.join(ours[channel])

        if not len(output):
            output = 'None'

        log.info(f'{ctx.author} viewed command whitelist for {ctx.channel}')
        await ctx.send(f':guard: **{output}**')
コード例 #26
0
ファイル: roles.py プロジェクト: Argavyon/aethersprite
async def roles(ctx: Context):
    "Manage your membership in available roles"

    expiry_raw = settings['roles.postexpiry'].get(ctx)
    expiry = seconds_to_str(expiry_raw)
    roles = settings['roles.catalog'].get(ctx)

    if roles is None or len(roles) == 0:
        await ctx.send(':person_shrugging: There are no available '
                       'self-service roles.')
        log.warn(f'{ctx.author} invoked roles self-service, but no roles are '
                 'available')

        return

    roles = roles[:10]
    embed = Embed(title=f':billed_cap: Available roles',
                  description='Use post reactions to manage role membership',
                  color=Color.purple())
    embed.set_footer(text=f'This post will be deleted in {expiry}.')
    count = 0

    for role in sorted(roles, key=lambda x: x.lower()):
        embed.add_field(name=f'{count}{DIGIT_SUFFIX} {role}', value='\u200b')
        count += 1

    msg: Message = await ctx.send(embed=embed)

    for i in range(0, count):
        await msg.add_reaction(f'{i}{DIGIT_SUFFIX}')

    posts[msg.id] = {
        'guild': ctx.guild.id,
        'channel': ctx.channel.id,
        'expiry': datetime.utcnow() + timedelta(seconds=expiry_raw)
    }

    log.info(f'{ctx.author} invoked roles self-service')
    loop.call_later(expiry_raw, _delete, msg.id)
    await ctx.message.delete()
コード例 #27
0
ファイル: settings.py プロジェクト: haliphax/aethersprite
    async def clear(self, ctx, name, *,
                    params: channel_arg = channel_arg.defaults()):
        """
        Reset setting <name> to its default value

        Use the 'channel' parameter to specify a channel other than the current one.

        Example: clear key channel=#lobby
        """

        channel = ctx.channel if 'channel' not in params else params['channel']

        if name not in settings:
            log.warn(f'{ctx.author} attempted to clear nonexistent setting: '
                     f'{name} in {channel}')
            await ctx.send(MSG_NO_SETTING)

            return

        settings[name].set(ctx, None, raw=True, channel=channel)
        await ctx.send(':negative_squared_cross_mark: Setting cleared.')
        log.info(f'{ctx.author} cleared setting {name} in {channel}')
コード例 #28
0
    async def add(self,
                  ctx,
                  command: str,
                  *,
                  params: channel_arg = channel_arg.defaults()):
        """
        Add the given command to the Only whitelist

        Enables <command> in this channel.

        Use the 'channel' parameter to specify a channel other than the current one.

        Example: only.add test channel=#lobby
        """

        channel = params['channel'] if 'channel' in params else ctx.channel
        chan_id = str(channel.id)
        guild = str(ctx.guild.id)

        if guild not in onlies:
            onlies[guild] = dict()

        ours = onlies[guild]

        if chan_id not in ours:
            ours[chan_id] = set([])

        ourchan = ours[chan_id]

        if command in ourchan:
            await ctx.send(f':newspaper: Already done.')

            return

        ourchan.add(command)
        ours[chan_id] = ourchan
        onlies[guild] = ours
        log.info(f'{ctx.author} added {command} to {channel} whitelist')
        await ctx.send(f':shield: Done.')
コード例 #29
0
ファイル: roles.py プロジェクト: haliphax/aethersprite
async def roles(ctx: Context):
    "Manage your membership in available roles"

    expiry_raw = settings['roles.postexpiry'].get(ctx)
    expiry = seconds_to_str(expiry_raw)
    roles = settings['roles.catalog'].get(ctx)

    if roles is None or len(roles) == 0:
        await ctx.send(':person_shrugging: There are no available '
                       'self-service roles.')
        log.warn(f'{ctx.author} invoked roles self-service, but no roles are '
                 'available')

        return

    msg = await _get_message(ctx, expiry=expiry)
    posts[msg.id] = {'guild': ctx.guild.id,
                     'channel': ctx.channel.id,
                     'expiry': datetime.utcnow()
                     + timedelta(seconds=expiry_raw)}

    log.info(f'{ctx.author} invoked roles self-service')
    loop.call_later(expiry_raw, _delete, msg.id)
    await ctx.message.delete()
コード例 #30
0
ファイル: settings.py プロジェクト: haliphax/aethersprite
    async def get(self, ctx, name: Optional[str] = None, *,
                  params: channel_arg = channel_arg.defaults()):
        """
        View a setting's value

        Use the 'channel' parameter to specify a channel other than the current one.

        Example: get key channel=#lobby
        """

        if name is None:
            settings_str = '**, **'.join(sorted(settings.keys()))
            await ctx.send(f':gear: All settings: **{settings_str}**')
            log.info(f'{ctx.author} viewed all settings')

            return

        channel = ctx.channel if 'channel' not in params else params['channel']
        val = settings[name].get(ctx, channel=channel)
        default = settings[name].default
        await ctx.send(f':gear: `{name}`\n'
                        f'>>> Value: `{repr(val)}`\n'
                        f'Default: `{repr(default)}`')
        log.info(f'{ctx.author} viewed setting {name} in {channel}')