import discord from discord.ext import commands import json import logging import math import psutil from datetime import datetime, timedelta import asyncio import async_timeout from .imports import checks import pytz import gspread from oauth2client.service_account import ServiceAccountCredentials config_dir = 'config/' admin_id_file = 'admin_ids' extension_dir = 'extensions' owner_id = 351794468870946827 embed_color = discord.Colour.from_rgb(49, 107, 111) bot_config_file = 'bot_config.json' invite_match = '(https?://)?(www.)?discord(app.com/(invite|oauth2)|.gg|.io)/[\w\d_\-?=&/]+' utils_log = logging.getLogger('utils') clock_emojis = ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚'] class Utils: def __init__(self, bot): self.bot = bot async def _4_hour_ping(self, channel, message, wait_time): channel = self.bot.get_channel(channel) time_now = datetime.utcnow() await channel.send(message) while True: if time_now < datetime.utcnow() - timedelta(seconds=wait_time+10): await channel.send(message) time_now = datetime.utcnow() await asyncio.sleep(wait_time/100) async def background_ping(self, ctx, i): def check(message): return message.author == ctx.guild.me and 'ping_test' in message.content msg = await self.bot.wait_for('message', timeout=5, check=check) self.bot.ping_times[i]['rec'] = msg @commands.command() async def channel_ping(self, ctx, wait_time: float=10, message: str='=bump', channel: int=265828729970753537): await ctx.send('Starting Background Process.') self.bot.loop.create_task(self._4_hour_ping(channel, message, wait_time)) @commands.command() @commands.is_owner() async def sysinfo(self, ctx): await ctx.send(f''' ```ml CPU Percentages: {psutil.cpu_percent(percpu=True)} Memory Usage: {psutil.virtual_memory().percent}% Disc Usage: {psutil.disk_usage("/").percent}% ```''') @commands.command(hidden=True) async def role(self, ctx, role): if ctx.guild.id == 396156980974059531 and role != 'Admin' and role != 'Admin Geeks': try: role = discord.utils.get(ctx.guild.roles, name=role) except Exception: await ctx.send('Unknown Role') else: if role is not None: await ctx.message.author.add_roles(role) await ctx.send("Roles Updated") else: await ctx.send('Unknown Role') else: await ctx.send("You are not authorized to send this command.") @commands.command(aliases=['oauth', 'link']) @commands.cooldown(1, 5, type=commands.BucketType.user) async def invite(self, ctx, guy: discord.User=None): """Shows you the bot's invite link. If you pass in an ID of another bot, it gives you the invite link to that bot. """ guy = guy or self.bot.user url = discord.utils.oauth_url(guy.id) await ctx.send(f'**{url}**') @staticmethod def create_date_string(time, time_now): diff = (time_now - time) date_str = time.strftime('%Y-%m-%d %H:%M:%S') return f"{diff.days} {'day' if diff.days == 1 else 'days'} " \ f"{diff.seconds // 3600} {'hour' if diff.seconds // 3600 == 1 else 'hours'} " \ f"{diff.seconds % 3600 // 60} {'minute' if diff.seconds % 3600 // 60 == 1 else 'minutes'} " \ f"{diff.seconds % 3600 % 60} {'second' if diff.seconds % 3600 % 60 == 1 else 'seconds'} ago.\n{date_str}" @commands.command() @commands.cooldown(1, 5, type=commands.BucketType.user) async def me(self, ctx): """Prints out your user information.""" em = discord.Embed(style='rich', title=f'{ctx.author.name}#{ctx.author.discriminator} ({ctx.author.display_name})', description=f'({ctx.author.id})', color=embed_color) em.set_thumbnail(url=f'{ctx.author.avatar_url}') em.add_field(name=f'Highest Role:', value=f'{ctx.author.top_role}', inline=True) em.add_field(name=f'Bot:', value=f'{ctx.author.bot}', inline=True) em.add_field(name=f'Joined Guild:', value=f'{self.create_date_string(ctx.author.joined_at, ctx.message.created_at)}', inline=False) em.add_field(name=f'Joined Discord:', value=f'{self.create_date_string(ctx.author.created_at, ctx.message.created_at)}', inline=False) em.add_field(name=f'Current Status:', value=f'{ctx.author.status}', inline=True) em.add_field(name=f"Currently{' '+ctx.author.activity.type.name.title() if ctx.author.activity else ''}:", value=f"{ctx.author.activity.name if ctx.author.activity else 'Not doing anything important.'}", inline=True) count = 0 async for message in ctx.channel.history(after=(ctx.message.created_at - timedelta(hours=1))): if message.author == ctx.author: count += 1 em.add_field(name=f'Activity:', value=f'You have sent {count} ' f'{"message" if count == 1 else "messages"} in the last hour to this channel.', inline=False) await ctx.send(embed=em) @commands.command() @commands.cooldown(1, 5, type=commands.BucketType.user) async def user(self, ctx, member: discord.Member): """Prints User information. should be in the form @Dusty.P#0001""" em = discord.Embed(style='rich', title=f'{member.name}#{member.discriminator} ({member.display_name})', description=f'({member.id})', color=embed_color) em.set_thumbnail(url=f'{member.avatar_url}') em.add_field(name=f'Highest Role:', value=f'{member.top_role}', inline=True) em.add_field(name=f'Bot:', value=f'{member.bot}', inline=True) em.add_field(name=f'Joined Guild:', value=f'{self.create_date_string(member.joined_at,ctx.message.created_at)}', inline=False) em.add_field(name=f'Joined Discord:', value=f'{self.create_date_string(member.created_at,ctx.message.created_at)}', inline=False) em.add_field(name=f'Current Status:', value=f'{member.status}', inline=True) em.add_field(name=f"Currently{' '+member.activity.type.name.title() if member.activity else ''}:", value=f"{member.activity.name if member.activity else 'Not doing anything important.'}", inline=True) count = 0 async for message in ctx.channel.history(after=(ctx.message.created_at - timedelta(hours=1))): if message.author == member: count += 1 em.add_field(name=f'Activity:', value=f'{member.display_name} has sent {count} ' f'{"message" if count == 1 else "messages"} in the last hour to this channel.', inline=False) await ctx.send(embed=em) @commands.command() @commands.cooldown(1, 5, type=commands.BucketType.user) async def ping(self, ctx, mode='normal', count: int=2): """Check the Bot\'s connection to Discord""" em = discord.Embed(style='rich', title=f'Pong 🏓', color=discord.Colour.green() ) msg = await ctx.send(embed=em) time1 = ctx.message.created_at time = (msg.created_at - time1).total_seconds() * 1000 em.description = f'''Response Time: **{math.ceil(time)}ms** Discord Latency: **{math.ceil(self.bot.latency*1000)}ms**''' await msg.edit(embed=em) if mode == 'comp': try: count = int(count) except Exception: await ctx.send('Not a valid count. Must be a whole number.') else: if count > 24: await ctx.send('24 Pings is the max allowed. Setting count to 24.', delete_after=5) count = 24 self.bot.ping_times = [] times = [] for i in range(count): self.bot.ping_times.append({}) self.bot.loop.create_task(self.background_ping(ctx, i)) await asyncio.sleep(0.1) self.bot.ping_times[i]['snd'] = await ctx.send('ping_test') now = datetime.utcnow() while 'rec' not in self.bot.ping_times[i]: now = datetime.utcnow() if now.timestamp() > self.bot.ping_times[i]['snd'].created_at.timestamp()+5: break if 'rec' in self.bot.ping_times[i]: time = now - self.bot.ping_times[i]['snd'].created_at time = time.total_seconds() times.append(time) value = f"Message Sent:" \ f"{datetime.strftime(self.bot.ping_times[i]['snd'].created_at, '%H:%M:%S.%f')}" \ f"Response Received: {datetime.strftime(now, '%H:%M:%S.%f')}" \ f"Total Time: {math.ceil(time * 1000)}ms" await self.bot.ping_times[i]['rec'].delete() em.add_field(name=f'Ping Test {i}', value=value, inline=True) else: em.add_field(name=f'Ping Test {i}', value='Timeout...', inline=True) total_time = 0 print(times) for time in times: total_time += time * 1000 em.add_field(value=f'Total Time for Comprehensive test: {math.ceil(total_time)}ms', name=f'Average: **{round(total_time/count,1)}ms**', inline=False) await msg.edit(embed=em) @commands.group(case_insensitive=True) async def admin(self, ctx): """Run help admin for more info""" pass @admin.command(name='new', aliases=['nr']) @commands.cooldown(1, 30, type=commands.BucketType.user) async def new_admin_request(self, ctx, *, request_msg=None): """Submit a new request for admin assistance. The admin will be notified when your request is made and it will be added to the request list for this guild. """ if ctx.guild: if request_msg is not None: if len(request_msg) < 1000: self.bot.con.run('insert into admin_requests (issuing_member_id, guild_orig, request_text,' 'request_time) values (%(member_id)s, %(guild_id)s, %(text)s, %(time)s)', {'member_id': ctx.author.id, 'guild_id': ctx.guild.id, 'text': request_msg, 'time': ctx.message.created_at}) channel = self.bot.con.one(f'select admin_chat from guild_config where guild_id = {ctx.guild.id}') if channel: chan = discord.utils.get(ctx.guild.channels, id=channel) msg = '' admin_roles = [] roles = self.bot.con.one(f'select admin_roles,rcon_admin_roles from guild_config where ' f'guild_id = %(id)s', {'id': ctx.guild.id}) request_id = self.bot.con.one(f'select id from admin_requests where ' f'issuing_member_id = %(member_id)s and request_time = %(time)s', {'member_id': ctx.author.id, 'time': ctx.message.created_at}) for item in roles: i = json.loads(item) for j in i: if i[j] not in admin_roles: admin_roles.append(i[j]) for role in admin_roles: msg = '{0} {1}'.format(msg, discord.utils.get(ctx.guild.roles, id=role).mention) msg += f"""New Request ID: {request_id} {ctx.author.mention} has requested assistance: ```{request_msg}``` Requested on: {datetime.strftime(ctx.message.created_at, '%Y-%m-%d at %H:%M:%S')} GMT""" await chan.send(msg) await ctx.send('The Admin have received your request.') else: await ctx.send('Request is too long, please keep your message to less than 1000 characters.') else: await ctx.send('Please include a message containing information about your request.') else: await ctx.send('This command must be run from inside a guild.') @admin.command(name='list', aliases=['lr']) @commands.cooldown(1, 5, type=commands.BucketType.user) async def list_admin_requests(self, ctx, assigned_to: discord.Member=None): """Returns a list of all active Admin help requests for this guild If a user runs this command it will return all the requests that they have submitted and are still open. - The [assigned_to] argument is ignored but will still give an error if an incorrect value is entered. If an admin runs this command it will return all the open requests for this guild. - If the [assigned_to] argument is included it will instead return all open requests that are assigned to the specified admin. """ em = discord.Embed(style='rich', title=f'Admin Help Requests', color=discord.Colour.green() ) if checks.is_admin(self.bot, ctx) or checks.is_rcon_admin(self.bot, ctx): if assigned_to is None: requests = self.bot.con.all(f'select * from admin_requests where guild_orig = %(guild_id)s ' f'and completed_time is null', {'guild_id': ctx.guild.id}) em.title = f'Admin help requests for {ctx.guild.name}' if requests: for request in requests: member = discord.utils.get(ctx.guild.members, id=request[1]) admin = discord.utils.get(ctx.guild.members, id=request[2]) title = f"""{'Request ID':^12}{'Requested By':^20}{'Assigned to':^20} {request[0]:^12}{member.display_name if member else 'None':^20} {admin.display_name if admin else 'None':^20}""" em.add_field(name='￲', value=f"""```{title} {request[4]} Requested on: {datetime.strftime(request[5], '%Y-%m-%d at %H:%M:%S')} GMT```""", inline=False) else: em.add_field(name='There are no pending requests for this guild.', value='￰', inline=False) else: if checks.check_admin_role(self.bot, ctx, assigned_to)\ or checks.check_rcon_role(self.bot, ctx, assigned_to): requests = self.bot.con.all('select * from admin_requests where assigned_to = %(admin_id)s ' 'and guild_orig = %(guild_id)s and completed_time is null', {'admin_id': assigned_to.id, 'guild_id': ctx.guild.id}) em.title = f'Admin help requests assigned to {assigned_to.display_name} in {ctx.guild.name}' if requests: for request in requests: member = discord.utils.get(ctx.guild.members, id=request[1]) em.add_field(name=f"Request ID: {request[0]} Requested By:" f"{member.display_name if member else 'None'}", value=f"""{request[4]} Requested on: {datetime.strftime(request[5], '%Y-%m-%d at %H:%M:%S')} GMT ￰""", inline=False) else: em.add_field(name=f'There are no pending requests for {assigned_to.display_name} this guild.', value='￰', inline=False) else: em.title = f'{assigned_to.display_name} is not an admin in this guild.' else: requests = self.bot.con.all('select * from admin_requests where issuing_member_id = %(member_id)s ' 'and guild_orig = %(guild_id)s and completed_time is null', {'member_id': ctx.author.id, 'guild_id': ctx.guild.id}) em.title = f'Admin help requests for {ctx.author.display_name}' if requests: for request in requests: admin = discord.utils.get(ctx.guild.members, id=request[2]) em.add_field(name=f"Request ID: {request[0]}" f"{' Assigned to: ' + admin.display_name if admin else ''}", value=f"""{request[4]} Requested on: {datetime.strftime(request[5], '%Y-%m-%d at %H:%M:%S')} GMT ￰""", inline=False) else: em.add_field(name='You have no pending Admin Help requests.', value='To submit a request please use `admin new `', inline=False) await ctx.send(embed=em) @admin.command(name='close') async def close_request(self, ctx, *, request_ids=None): """Allows Admin to close admin help tickets. [request_id] must be a valid integer pointing to an open Request ID """ if checks.is_admin(self.bot, ctx) or checks.is_rcon_admin(self.bot, ctx): if request_ids: request_ids = request_ids.replace(' ', '').split(',') for request_id in request_ids: try: request_id = int(request_id) except Exception: await ctx.send(f'{request_id} is not a valid request id.') else: request = self.bot.con.one(f'select * from admin_requests where id = %(request_id)s', {'request_id': request_id}) if request: if request[3] == ctx.guild.id: if request[6] is None: self.bot.con.run('update admin_requests set completed_time = %(time_now)s where ' 'id = %(request_id)s', {'time_now': ctx.message.created_at, 'request_id': request_id}) await ctx.send(f'Request {request_id} by ' f'{ctx.guild.get_member(request[1]).display_name}' f' has been marked complete.') else: await ctx.send(f'Request {request_id} is already marked complete.') else: await ctx.send(f'Request {request_id} is not registered to this guild.') else: await ctx.send(f'{request_id} is not a valid request id.') else: await ctx.send('You must include at least one request id to close.') else: await ctx.send(f'You are not authorized to run this command.') @commands.command(name='weather', aliases=['wu']) @commands.cooldown(5, 15, type=commands.BucketType.default) async def get_weather(self, ctx, *, location='palmer ak'): """Gets the weather data for the location provided, If no location is included then it will get the weather for the Bot's home location. """ try: url = f'http://autocomplete.wunderground.com/aq?query={location}&format=JSON' with async_timeout.timeout(10): async with self.bot.aio_session.get(url) as response: data = await response.json() link = data['RESULTS'][0]['l'] url = f'http://api.wunderground.com/api/88e14343b2dd6d8e/geolookup/conditions/{link}.json' with async_timeout.timeout(10): async with self.bot.aio_session.get(url) as response: data = json.loads(await response.text()) utils_log.info(data) em = discord.Embed() em.title = f"Weather for {data['current_observation']['display_location']['full']}" em.url = data['current_observation']['forecast_url'] em.description = data['current_observation']['observation_time'] em.set_thumbnail(url=data['current_observation']['icon_url']) em.set_footer(text='Data provided by wunderground.com', icon_url=data['current_observation']['image']['url']) value_str = f'''``` {'Temp:':<20}{data['current_observation']['temperature_string']:<22} {'Feels Like:':<20}{data['current_observation']['feelslike_string']:<22} {'Relative Humidity:':<20}{data['current_observation']['relative_humidity']:<22} {'Wind:':<5}{data['current_observation']['wind_string']:^44} ```''' em.add_field(name=f"Current Conditions: {data['current_observation']['weather']}", value=value_str, inline=False) await ctx.send(embed=em) except IndexError: await ctx.send('Can\'t find that location, please try again.') @commands.command(name='localtime', aliases=['time', 'lt']) @commands.cooldown(1, 3, type=commands.BucketType.user) async def get_localtime(self, ctx, timezone: str='Anchorage'): em = discord.Embed() try: tz = pytz.timezone(timezone) localtime = datetime.now(tz=tz) em.title = f'{clock_emojis[(localtime.hour % 12)]} {tz}' em.description = localtime.strftime('%c') em.colour = embed_color await ctx.send(embed=em) except pytz.exceptions.UnknownTimeZoneError: for tz in pytz.all_timezones: if timezone.lower() in tz.lower(): localtime = datetime.now(tz=pytz.timezone(tz)) em.title = f'{clock_emojis[(localtime.hour % 12)]} {tz}' em.description = localtime.strftime('%c') em.colour = embed_color await ctx.send(embed=em) return em.title = 'Unknown Timezone.' em.colour = discord.Colour.red() await ctx.send(embed=em) @commands.command(name='purge', aliases=['clean', 'erase']) @commands.cooldown(1, 3, type=commands.BucketType.user) async def purge_messages(self, ctx, number: int=20, member: discord.Member=None): def is_me(message): if message.author == self.bot.user: return True prefixes = self.bot.con.one('select prefix from guild_config where guild_id = %(id)s', {'id': ctx.guild.id}) if prefixes: for prefix in prefixes: if message.content.startswith(prefix): return True return False return message.content.startswith(self.bot.default_prefix) def is_member(message): return message.author == member def is_author(message): return message.author == ctx.author if checks.is_admin(self.bot, ctx): if member: deleted = await ctx.channel.purge(limit=number, check=is_member) if member != ctx.author: await ctx.message.delete() else: deleted = await ctx.channel.purge(limit=number, check=is_me) else: deleted = await ctx.channel.purge(limit=number, check=is_author) em = discord.Embed(title='❌ Purge', colour=discord.Colour.red()) em.description = f'Deleted {len(deleted)} messages.' await ctx.send(embed=em, delete_after=5) @commands.command(name='purge_all', aliases=['cls', 'clear']) @commands.cooldown(1, 3, type=commands.BucketType.user) async def purge_all(self, ctx, number: int=20, contents: str='all'): if checks.is_admin(self.bot, ctx): if contents != 'all': deleted = await ctx.channel.purge(limit=number, check=lambda message: message.content == contents) else: deleted = await ctx.channel.purge(limit=number) em = discord.Embed(title='❌ Purge', colour=discord.Colour.red()) em.description = f'Deleted {len(deleted)} messages.' if contents != ctx.message.content and contents != 'all': await ctx.message.delete() await ctx.send(embed=em, delete_after=5) @commands.command(name='google', aliases=['g', 'search']) async def google_search(self, ctx, *, search): res = self.bot.gcs_service.cse().list(q=search, cx=self.bot.bot_secret['cx']).execute() results = res['items'][:4] em = discord.Embed() em.title = f'Google Search' em.description = f'Top 4 results for "{search}"' em.colour = embed_color for result in results: em.add_field(name=f'{result["title"]}', value=f'{result["snippet"]}\n{result["link"]}') await ctx.send(embed=em) @commands.command(hidden=True, name='sheets') async def google_sheets(self, ctx, member: discord.Member): if checks.is_admin(self.bot, ctx): scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive'] credentials = ServiceAccountCredentials.from_json_keyfile_name('config/google_client_secret.json', scope) gc = gspread.authorize(credentials) sh = gc.open_by_key(self.bot.bot_secret['sheet']) ws = sh.worksheet('Current Whitelist') names = ws.col_values('3') steam = ws.col_values('6') tier = ws.col_values('5') patron = ws.col_values('4') em = discord.Embed() em.title = f'User Data from Whitelist Sheet' em.colour = embed_color for i, name in enumerate(names): if member.name.lower() in name.lower()\ or member.display_name.lower() in name.lower()\ or name.lower() in member.name.lower()\ or name.lower() in member.display_name.lower(): em.add_field(name=name, value=f'Steam ID: {steam[i]}\nPatreon Level: {tier[i]}\nPatron of: {patron[i]}') await ctx.send(embed=em) def setup(bot): bot.add_cog(Utils(bot))