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))))
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))
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)
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)
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)
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)
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)
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 )
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)))
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()
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)
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)
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)
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)
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)
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)
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)
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}')