Esempio n. 1
0
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()
Esempio n. 2
0
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))
Esempio n. 3
0
    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}')
Esempio n. 4
0
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()
Esempio n. 5
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}')
Esempio n. 6
0
async def check_name_only(ctx: Context):
    "If the bot wasn't mentioned, refuse the command."

    # don't bother with DMs
    if isinstance(ctx.channel, DMChannel):
        return True

    if (settings['nameonly'].get(ctx) \
                or settings['nameonly.channel'].get(ctx)) \
            and not ctx.bot.user.mentioned_in(ctx.message):
        log.warn(f'{ctx.author} attempted command without mentioning bot')

        return False

    return True
Esempio n. 7
0
    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}')
Esempio n. 8
0
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()
Esempio n. 9
0
    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}')
Esempio n. 10
0
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()
Esempio n. 11
0
async def on_ready():
    "Clear expired/missing roles posts on startup."

    # clean up missing directories
    for guild_id, directory in directories.items():
        try:
            guild: Guild = bot.get_guild(int(guild_id))
            chan: TextChannel = guild.get_channel(directory['channel'])
            msg = await chan.fetch_message(directory['message'])
        except NotFound:
            log.warn(f'Deleted missing directory post for {guild_id}')
            del directories[guild_id]

    # clean up expired posts
    now = datetime.utcnow()

    for id, msg in posts.items():
        if msg['expiry'] <= now:
            _delete(id)
        else:
            expiry: datetime = msg['expiry']
            diff = (expiry - now).total_seconds()
            loop.call_later(diff, _delete, id)
            log.debug(f'Scheduled deletion of self-service post {id}')
Esempio n. 12
0
    async def set(self, ctx, name: typing.Optional[str] = None, *value):
        """
        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.
        """

        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

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

            return

        val = ' '.join(value)

        if not len(val):
            val = settings[name].get(ctx)
            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}')
        elif settings[name].set(ctx, val):
            await ctx.send(f':thumbsup: Value updated.')
            log.info(f'{ctx.author} updated setting {name}: {val}')
        else:
            await ctx.send(f':thumbsdown: Error updating value.')
            log.warn(f'{ctx.author} failed to update setting {name}: {val}')