Пример #1
0
    async def ping(self, ctx):
        '''Check response time.'''

        msg = await ctx.send('Wait...')

        await msg.edit(content='Response: {}.\nGateway: {}'.format(
            pretty_timedelta(msg.created_at - ctx.message.created_at),
            pretty_timedelta(timedelta(seconds=self.bot.latency))))
Пример #2
0
	async def remindme(self, ctx, *, when_and_what: ReminderConverter()):
		'''Create a new reminder.'''

		now, when, message = when_and_what

		if when < now:
			raise commands.CommandError('Specified time is in the past.')

		if when - now > MAX_DELTA:
			raise commands.CommandError('Sorry, can\'t remind in more than a year in the future.')

		if message is not None and len(message) > 1024:
			raise commands.CommandError('Sorry, keep the message below 1024 characters!')

		count = await self.db.fetchval('SELECT COUNT(id) FROM remind WHERE user_id=$1', ctx.author.id)
		if count > MAX_REMINDERS:
			raise commands.CommandError(f'Sorry, you can\'t have more than {MAX_REMINDERS} active reminders at once.')

		await self.db.execute(
			'INSERT INTO remind (guild_id, channel_id, user_id, made_on, remind_on, message) VALUES ($1, $2, $3, $4, $5, $6)',
			ctx.guild.id, ctx.channel.id, ctx.author.id, now, when, message
		)

		self.timer.maybe_restart(when)

		remind_in = when - now
		remind_in += timedelta(microseconds=1000000 - (remind_in.microseconds % 1000000))

		await ctx.send('You will be reminded in {}.'.format(pretty_timedelta(remind_in)))

		log.info('%s set a reminder for %s.', po(ctx.author), pretty_datetime(when))
Пример #3
0
    async def ban_complete(self, record):
        guild_id = record.get('guild_id')
        user_id = record.get('user_id')
        mod_id = record.get('mod_id')
        duration = record.get('duration')
        userdata = loads(record.get('userdata'))
        reason = record.get('reason')

        guild = self.bot.get_guild(guild_id)
        if guild is None:
            return

        mod = guild.get_member(mod_id)
        pretty_mod = '(ID: {0})'.format(
            str(mod_id)) if mod is None else po(mod)

        member = FakeUser(user_id, guild, **userdata)

        try:
            await guild.unban(
                member,
                reason='Completed tempban issued by {0}'.format(pretty_mod))
        except discord.HTTPException:
            return  # rip :)

        self.bot.dispatch('log',
                          guild,
                          member,
                          action='TEMPBAN COMPLETED',
                          severity=Severity.RESOLVED,
                          responsible=pretty_mod,
                          duration=pretty_timedelta(duration),
                          reason=reason)
Пример #4
0
    async def info(self, ctx, *, member: discord.Member = None):
        '''Display information about user or self.'''

        member = member or ctx.author

        e = discord.Embed(description='')

        if member.bot:
            e.description = 'This account is a bot.\n\n'

        e.description += member.mention

        e.add_field(name='Status', value=member.status)

        if member.activity:
            e.add_field(name='Activity', value=member.activity.name)

        e.set_author(name=str(member), icon_url=member.avatar_url)

        now = datetime.utcnow()
        created = member.created_at
        joined = member.joined_at

        e.add_field(name='Account age',
                    value='{0} • Created {1}'.format(
                        pretty_timedelta(now - created),
                        pretty_datetime(created)),
                    inline=False)

        e.add_field(name='Member for',
                    value='{0} • Joined {1}'.format(
                        pretty_timedelta(now - joined),
                        pretty_datetime(joined)))

        if len(member.roles) > 1:
            e.add_field(name='Roles',
                        value=' '.join(role.mention
                                       for role in reversed(member.roles[1:])),
                        inline=False)

        e.set_footer(text='ID: ' + str(member.id))

        await ctx.send(embed=e)
Пример #5
0
    async def helper_purge(self):
        guild = self.bot.get_guild(AHK_GUILD_ID)
        if guild is None:
            return

        role = guild.get_role(HELPERS_ROLE_ID)
        if role is None:
            return

        past = datetime.utcnow() - INACTIVITY_LIMIT

        all_helpers = list(member for member in guild.members
                           if role in member.roles)
        helpers = list(member for member in all_helpers
                       if member.joined_at < past)

        spare = await self.bot.db.fetch(
            'SELECT user_id FROM seen WHERE guild_id=$1 AND user_id=ANY($2::bigint[]) AND seen>$3',
            AHK_GUILD_ID, list(member.id for member in helpers), past)

        spare_ids = list(record.get('user_id') for record in spare)
        remove = list(member for member in helpers
                      if member.id not in spare_ids)

        if not remove:
            return

        log.info('About to purge %s helpers. Current list: %s', len(remove),
                 ', '.join(list(str(member.id) for member in all_helpers)))

        log.info('Removing inactive helpers:\n%s',
                 '\n'.join(list(po(member) for member in remove)))

        reason = 'Removed helper inactive for over {0}.'.format(
            pretty_timedelta(INACTIVITY_LIMIT))

        for member in remove:
            try:
                await member.remove_roles(role, reason=reason)
            except discord.HTTPException as e:
                self.bot.dispatch(
                    'log',
                    guild,
                    member,
                    action='FAILED REMOVING HELPER',
                    reason='Failed removing role.\n\nException:\n```{}```'.
                    format(str(e)),
                )
                continue

            self.bot.dispatch('log',
                              guild,
                              member,
                              action='REMOVE HELPER',
                              reason=reason)
Пример #6
0
    async def newusers(self, ctx, *, count=5):
        '''List newly joined members.'''

        count = min(max(count, 5), 25)

        now = datetime.utcnow()
        e = discord.Embed()

        for idx, member in enumerate(
                sorted(ctx.guild.members,
                       key=lambda m: m.joined_at,
                       reverse=True)):
            if idx >= count:
                break

            value = 'Joined {0} ago\nCreated {1} ago'.format(
                pretty_timedelta(now - member.joined_at),
                pretty_timedelta(now - member.created_at))
            e.add_field(name=po(member), value=value, inline=False)

        await ctx.send(embed=e)
Пример #7
0
    async def _about_bot(self, ctx):
        e = disnake.Embed(
            title='Click here to add the bot to your own server!',
            description=
            f'{self.get_last_commits()}\n\n[Support server here!]({self.bot.support_link})',
            url=self.bot.invite_link)

        owner = self.bot.get_user(self.bot.owner_id)
        e.set_author(name=str(owner), icon_url=owner.display_avatar.url)

        e.add_field(name='Developer',
                    value=str(self.bot.get_user(self.bot.owner_id)))

        invokes = await self.db.fetchval('SELECT COUNT(*) FROM log')
        e.add_field(name='Command invokes', value='{0:,d}'.format(invokes))

        guilds, text, voice, users = 0, 0, 0, 0

        for guild in self.bot.guilds:
            guilds += 1
            users += len(guild.members)
            for channel in guild.channels:
                if isinstance(channel, disnake.TextChannel):
                    text += 1
                elif isinstance(channel, disnake.VoiceChannel):
                    voice += 1

        unique = len(self.bot.users)

        e.add_field(name='Servers', value=str(guilds))

        memory_usage = self.process.memory_full_info().uss / 1024**2
        cpu_usage = self.process.cpu_percent() / psutil.cpu_count()

        e.add_field(name='Process',
                    value='CPU: {0:.2f}%\nMemory: {1:.2f} MiB'.format(
                        cpu_usage, memory_usage))

        e.add_field(name='Members',
                    value='{0:,d} total\n{1:,d} unique'.format(users, unique))
        e.add_field(
            name='Channels',
            value='{0:,d} total\n{1:,d} text channels\n{2:,d} voice channels'.
            format(text + voice, text, voice))

        now = datetime.utcnow()
        e.set_footer(text='Last restart {0} ago'.format(
            pretty_timedelta(now - self.bot.startup_time)))

        await ctx.send(embed=e)
Пример #8
0
	async def craft_page(self, e, page, entries):
		now = datetime.utcnow()

		e.set_author(name=self.author.name, icon_url=self.author.avatar_url)
		e.description = 'All your reminders for this server.'

		for _id, guild_id, channel_id, user_id, made_on, remind_on, message in entries:
			delta = remind_on - now

			time_text = pretty_timedelta(delta)
			e.add_field(
				name=f'{_id}: {time_text}',
				value=shorten(message, 256) if message is not None else DEFAULT_REMINDER_MESSAGE,
				inline=False
			)
Пример #9
0
    async def craft_page(self, e: discord.Embed, page, entries):
        e.description = f'{len(self.entries)} active tempban(s).'

        now = datetime.utcnow()

        for record in entries:
            _id = record.get('id')

            userdata = loads(record.get('userdata'))
            name = userdata['name']
            discrim = userdata['discriminator']

            user_id = record.get('user_id')

            banner = self.bot.get_user(record.get('mod_id'))
            when = record.get('created_at')
            duration = record.get('duration')

            e.add_field(
                name=f'{_id}: {name}#{discrim} ({user_id})',
                value='Banned: {0}\nBanner: {1}\nDuration: {2}\nUnban in {3}'.
                format(pretty_datetime(when), banner or 'UNKNOWN',
                       pretty_timedelta(duration),
                       pretty_timedelta(now - when + duration)))
Пример #10
0
    async def dispatch(self):
        # don't run timer before bot is ready
        await self.bot.wait_until_ready()

        try:
            while True:
                # fetch next record
                record = await self.get_record()

                # if none was found, sleep for 40 days and check again
                if record is None:
                    log.debug('No record found for %s, sleeping',
                              self.event_name)
                    await asyncio.sleep(self.MAX_SLEEP.total_seconds())
                    continue

                # we are now with this record
                self.record = record

                # get datetime again in case query took a lot of time
                now = datetime.utcnow()
                then = self.when(record)
                dt = then - now

                # if the next record is in the future, sleep until it should be invoked
                if now < then:
                    log.debug('%s dispatching in %s', self.event_name,
                              pretty_timedelta(then - now))
                    await asyncio.sleep(dt.total_seconds())

                await self.cleanup_record(record)
                self.record = None

                log.debug('Dispatching event %s', self.event_name)

                # run it
                self.bot.dispatch(self.event_name, record)

        except (disnake.ConnectionClosed,
                asyncpg.PostgresConnectionError) as e:
            # if anything happened, sleep for 15 seconds then attempt a restart
            log.warning(
                'DatabaseTimer got exception %s: attempting restart in 15 seconds',
                str(e))

            await asyncio.sleep(15)
            self.restart_task()
Пример #11
0
    async def update_page_embed(self, embed, page, entries):
        now = datetime.utcnow()

        embed.clear_fields()

        for record in entries:
            _id = record.get('id')
            remind_on = record.get('remind_on')
            message = record.get('message')

            delta = remind_on - now

            time_text = pretty_timedelta(delta)
            embed.add_field(name=f'{_id}: {time_text}',
                            value=shorten(message, 256) if message is not None
                            else DEFAULT_REMINDER_MESSAGE,
                            inline=False)
Пример #12
0
    async def craft_page(self, e, page, entries):
        now = datetime.utcnow()

        e.set_author(name=self.author.name, icon_url=self.author.avatar_url)
        e.description = 'All your reminders for this server.'

        for record in entries:
            _id = record.get('id')
            remind_on = record.get('remind_on')
            message = record.get('message')

            delta = remind_on - now

            time_text = pretty_timedelta(delta)
            e.add_field(name=f'{_id}: {time_text}',
                        value=shorten(message, 256)
                        if message is not None else DEFAULT_REMINDER_MESSAGE,
                        inline=False)
Пример #13
0
    async def mute_complete(self, record):
        conf = await self.config.get_entry(record.get('guild_id'))
        mute_role = conf.mute_role

        if mute_role is None:
            return

        guild_id = record.get('guild_id')
        user_id = record.get('user_id')
        mod_id = record.get('mod_id')
        duration = record.get('duration')
        reason = record.get('reason')

        guild = self.bot.get_guild(guild_id)
        if guild is None:
            return

        member = guild.get_member(user_id)
        if member is None:
            return

        mod = guild.get_member(mod_id)
        pretty_mod = '(ID: {0})'.format(
            str(mod_id)) if mod is None else po(mod)

        try:
            await member.remove_roles(
                mute_role,
                reason='Completed tempmute issued by {0}'.format(pretty_mod))
        except discord.HTTPException:
            return

        self.bot.dispatch('log',
                          guild,
                          member,
                          action='TEMPMUTE COMPLETED',
                          severity=Severity.RESOLVED,
                          responsible=pretty_mod,
                          duration=pretty_timedelta(duration),
                          reason=reason)
Пример #14
0
    async def tempban(self,
                      ctx,
                      member: Union[disnake.Member, BannedMember],
                      amount: TimeMultConverter,
                      unit: TimeDeltaConverter,
                      *,
                      reason: reason_converter = None):
        '''Temporarily ban a member. Requires Ban Members perms.'''

        now = datetime.utcnow()
        duration = amount * unit
        until = now + duration

        on_guild = isinstance(member, disnake.Member)

        if on_guild:
            if await ctx.is_mod(member):
                raise commands.CommandError('Can\'t tempban this member.')
        else:
            user: disnake.User = member.user
            member = FakeUser(user.id,
                              ctx.guild,
                              name=user.name,
                              avatar_url=str(user.display_avatar),
                              discriminator=user.discriminator)

        is_tempbanned = await self.bot.db.fetchval(
            'SELECT id FROM mod_timer WHERE guild_id=$1 AND user_id=$2 AND event=$3 AND completed=FALSE',
            ctx.guild.id, member.id, 'BAN')

        if is_tempbanned:
            raise commands.CommandError(
                'This member is already tempbanned. Use `alterban` to change duration?'
            )

        if duration > MAX_DELTA:
            raise commands.CommandError(
                'Can\'t tempban for longer than {0}. Please `ban` instead.'.
                format(pretty_timedelta(MAX_DELTA)))

        pretty_duration = pretty_timedelta(duration)

        # only send DMs for initial bans
        if on_guild:
            ban_msg = 'You have received a ban lasting {0} from {1}.\n\nReason:\n```\n{2}\n```'.format(
                pretty_duration, ctx.guild.name, reason)

            try:
                await member.send(ban_msg)
            except disnake.HTTPException:
                pass

        # reason we start a transaction is so it auto-rollbacks if the CommandError on ban fails is raised
        async with self.db.acquire() as con:
            async with con.transaction():
                try:
                    await self.db.execute(
                        'INSERT INTO mod_timer (guild_id, user_id, mod_id, event, created_at, duration, reason, userdata) '
                        'VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
                        ctx.guild.id, member.id, ctx.author.id, 'BAN', now,
                        duration, reason, self._craft_user_data(member))
                except UniqueViolationError:
                    # this *should* never happen but I'd rather leave it in
                    raise commands.CommandError(
                        'Member is already tempbanned.')

                try:
                    await ctx.guild.ban(member,
                                        delete_message_days=0,
                                        reason=reason)
                except disnake.HTTPException:
                    raise commands.CommandError('Failed tempbanning member.')

        self.event_timer.maybe_restart(until)

        try:
            await ctx.send('{0} tempbanned for {1}.'.format(
                str(member), pretty_duration))
        except disnake.HTTPException:
            pass

        self.bot.dispatch('log',
                          ctx.guild,
                          member,
                          action='TEMPBAN',
                          severity=Severity.HIGH,
                          message=ctx.message,
                          responsible=po(ctx.author),
                          duration=pretty_duration,
                          reason=reason)
Пример #15
0
    async def server(self, ctx):
        """Show various information about the server."""

        guild = ctx.guild

        desc = dict(ID=guild.id, )

        e = discord.Embed(title=guild.name,
                          description='\n'.join(
                              '**{}**: {}'.format(key, value)
                              for key, value in desc.items()),
                          timestamp=guild.created_at)

        e.add_field(name='Owner', value=guild.owner.mention)

        e.add_field(name='Region', value=str(guild.region))

        # CHANNELS

        channels = {
            discord.TextChannel: 0,
            discord.VoiceChannel: 0,
        }

        for channel in guild.channels:
            for channel_type in channels:
                if isinstance(channel, channel_type):
                    channels[channel_type] += 1

        channel_desc = '{} {}\n{} {}'.format(
            '<:text_channel:635021087247171584>',
            channels[discord.TextChannel],
            '<:voice_channel:635021113134546944>',
            channels[discord.VoiceChannel])

        e.add_field(name='Channels', value=channel_desc)

        # FEATURES

        if guild.features:
            e.add_field(name='Features',
                        value='\n'.join('• ' +
                                        feature.replace('_', ' ').title()
                                        for feature in guild.features))

        # MEMBERS

        statuses = dict(online=0, idle=0, dnd=0, offline=0)

        total_online = 0

        for member in guild.members:
            status_str = str(member.status)
            if status_str != "offline":
                total_online += 1
            statuses[status_str] += 1

        member_desc = '{} {} {} {} {} {} {} {}'.format(
            '<:online:635022092903120906>', statuses['online'],
            '<:idle:635022068290813952>', statuses['idle'],
            '<:dnd:635022045952081941>', statuses['dnd'],
            '<:offline:635022116462264320>', statuses['offline'])

        e.add_field(name='Members ({}/{})'.format(total_online,
                                                  len(guild.members)),
                    value=member_desc,
                    inline=False)

        # SERVER BOOST

        boost_desc = 'Level {} - {} Boosts'.format(
            guild.premium_tier, guild.premium_subscription_count)

        if guild.premium_subscribers:
            booster = guild.premium_subscribers[0]
            boost_desc += '\nLast boost by {} {} ago'.format(
                booster.mention,
                pretty_timedelta(datetime.utcnow() - booster.premium_since))

        e.add_field(name='Server boost', value=boost_desc)

        e.set_thumbnail(url=guild.icon_url)
        e.set_footer(text='Created')

        await ctx.send(embed=e)
Пример #16
0
    async def tempban(self,
                      ctx,
                      member: discord.Member,
                      amount: TimeMultConverter,
                      unit: TimeDeltaConverter,
                      *,
                      reason: reason_converter = None):
        '''Temporarily ban a member. Requires Ban Members perms. Same formatting as `tempmute` explained above.'''

        now = datetime.utcnow()
        duration = amount * unit
        until = now + duration

        if await ctx.is_mod(member):
            raise commands.CommandError('Can\'t tempban this member.')

        if duration > MAX_DELTA:
            raise commands.CommandError(
                'Can\'t tempban for longer than {0}. Please `ban` instead.'.
                format(pretty_timedelta(MAX_DELTA)))

        pretty_duration = pretty_timedelta(duration)

        ban_msg = 'You have received a ban lasting {0} from {1}.\n\nReason:\n```\n{2}\n```'.format(
            pretty_duration, ctx.guild.name, reason)

        try:
            await member.send(ban_msg)
        except discord.HTTPException:
            pass

        async with self.db.acquire() as con:
            async with con.transaction():
                try:
                    await self.db.execute(
                        'INSERT INTO mod_timer (guild_id, user_id, mod_id, event, created_at, duration, reason, userdata) '
                        'VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
                        ctx.guild.id, member.id, ctx.author.id, 'BAN', now,
                        duration, reason, self._craft_user_data(member))
                except UniqueViolationError:
                    raise commands.CommandError('Member is already banned.')

                try:
                    await ctx.guild.ban(member,
                                        delete_message_days=0,
                                        reason=reason)
                except discord.HTTPException:
                    raise commands.CommandError('Failed banning member.')

        self.event_timer.maybe_restart(until)

        try:
            await ctx.send('{0} tempbanned for {1}.'.format(
                str(member), pretty_duration))
        except discord.HTTPException:
            pass

        self.bot.dispatch('log',
                          ctx.guild,
                          member,
                          action='TEMPBAN',
                          severity=Severity.HIGH,
                          message=ctx.message,
                          responsible=po(ctx.author),
                          duration=pretty_duration,
                          reason=reason)
Пример #17
0
    async def tempmute(self,
                       ctx,
                       member: discord.Member,
                       amount: TimeMultConverter,
                       unit: TimeDeltaConverter,
                       *,
                       reason: reason_converter = None):
        '''
		Temporarily mute a member. Requires Manage Role perms. Example: `tempmute @member 1 day Reason goes here.`
		'''

        now = datetime.utcnow()
        duration = amount * unit
        until = now + duration

        if await ctx.is_mod(member):
            raise commands.CommandError('Can\'t mute this member.')

        if duration > MAX_DELTA:
            raise commands.CommandError(
                'Can\'t tempmute for longer than {0}. Use `mute` instead.'.
                format(pretty_timedelta(MAX_DELTA)))

        conf = await self.config.get_entry(ctx.guild.id)
        mute_role = conf.mute_role

        if mute_role is None:
            raise commands.CommandError('Mute role not set or not found.')

        async with self.db.acquire() as con:
            async with con.transaction():
                try:
                    await con.execute(
                        'INSERT INTO mod_timer (guild_id, user_id, mod_id, event, created_at, duration, reason, userdata) '
                        'VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
                        ctx.guild.id, member.id, ctx.author.id, 'MUTE', now,
                        duration, reason, self._craft_user_data(member))
                except UniqueViolationError:
                    raise commands.CommandError('Member is already muted.')

                try:
                    await member.add_roles(mute_role)
                except discord.HTTPException:
                    raise commands.CommandError('Failed adding mute role.')

        self.event_timer.maybe_restart(until)

        pretty_duration = pretty_timedelta(duration)

        try:
            await ctx.send('{0} tempmuted for {1}.'.format(
                str(member), pretty_duration))
        except discord.HTTPException:
            pass

        self.bot.dispatch('log',
                          ctx.guild,
                          member,
                          action='TEMPMUTE',
                          severity=Severity.LOW,
                          message=ctx.message,
                          responsible=po(ctx.author),
                          duration=pretty_duration,
                          reason=reason)
Пример #18
0
    async def alterban(self, ctx: AceContext, member: BannedMember,
                       amount: TimeMultConverter, *, unit: TimeDeltaConverter):
        '''Set a new duration for a tempban.'''

        duration = amount * unit

        if duration > MAX_DELTA:
            raise commands.CommandError(
                'Can\'t tempban for longer than {0}. Please `ban` instead.'.
                format(pretty_timedelta(MAX_DELTA)))

        record = await self.bot.db.fetchrow(
            'SELECT * FROM mod_timer WHERE guild_id=$1 AND user_id=$2 AND event=$3 AND completed=FALSE',
            ctx.guild.id, member.user.id, 'BAN')

        if record is None:
            raise commands.CommandError(
                'Could not find a tempban referencing this member.')

        now = datetime.utcnow()
        old_now = record.get('created_at')
        old_duration = record.get('duration')
        new_until = old_now + duration
        old_until = old_now + old_duration

        if duration == old_duration:
            raise commands.CommandError(
                'New ban duration is the same as the current duration.')

        old_duration_pretty = pretty_timedelta(old_duration)
        old_end_pretty = pretty_timedelta(old_until - now)
        duration_pretty = pretty_timedelta(duration)

        prompt = f'The previous ban duration was {old_duration_pretty} and will end in {old_end_pretty}.\n\n'

        if new_until < now:
            prompt += 'The new duration ends in the past and will cause an immediate unban.'
        else:
            prompt += f'The new ban duration is {duration_pretty} and will end in {pretty_timedelta(new_until - now)}.'

        should_continue = await ctx.prompt(
            title='Are you sure you want to alter this tempban?',
            prompt=prompt)

        if not should_continue:
            return

        await self.db.execute(
            'UPDATE mod_timer SET duration=$1 WHERE guild_id=$2 AND user_id=$3 AND event=$4',
            duration, ctx.guild.id, member.user.id, 'BAN')

        self.event_timer.restart_task()

        self.bot.dispatch(
            'log',
            ctx.guild,
            member.user,
            action='TEMPBAN UPDATE',
            severity=Severity.HIGH,
            message=ctx.message,
            responsible=po(ctx.author),
            duration=duration_pretty,
            reason=f'Previous duration was {old_duration_pretty}')