You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Geeksbot/exts/utils.py

446 lines
25 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

import discord
from discord.ext import commands
import json
from srcds import rcon as rcon_con
import time, logging, math, psutil
from datetime import datetime, timedelta
import asyncio, inspect
import aiohttp, async_timeout
from bs4 import BeautifulSoup as bs
import traceback
from .imports import checks
from .imports.utils import paginate
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\nCPU Percentages: {psutil.cpu_percent(percpu=True)}\nMemory Usage: {psutil.virtual_memory().percent}%\nDisc 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:
await ctx.send('Unknown Role')
else:
if role != 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}**')
def create_date_string(self, 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'} {diff.seconds // 3600} {'hour' if diff.seconds // 3600 == 1 else 'hours'} {diff.seconds % 3600 // 60} {'minute' if diff.seconds % 3600 // 60 == 1 else 'minutes'} {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} {"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.
<member> 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} {"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**\nDiscord Latency: **{math.ceil(self.bot.latency*1000)}ms**'
await msg.edit(embed=em)
if mode == 'comp':
try:
count = int(count)
except:
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: {datetime.strftime(self.bot.ping_times[i]['snd'].created_at, '%H:%M:%S.%f')}\nResponse Received: {datetime.strftime(now, '%H:%M:%S.%f')}\nTotal 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 != 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 guild_id = {ctx.guild.id}')
request_id = self.bot.con.one(f'select id from admin_requests where 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}\n{ctx.author.mention} has requested assistance:\n```{request_msg}```\nRequested 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 == None:
requests = self.bot.con.all(f'select * from admin_requests where guild_orig = %(guild_id)s 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}\n{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}\n\n{request[4]}\n\nRequested 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 asigned 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: {member.display_name if member else 'None'}", value=f"{request[4]}\nRequested on: {datetime.strftime(request[5], '%Y-%m-%d at %H:%M:%S')} GMT\n", 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]}{' Assigned to: ' + admin.display_name if admin else ''}", value=f"{request[4]}\nRequested on: {datetime.strftime(request[5], '%Y-%m-%d at %H:%M:%S')} GMT\n", inline=False)
else:
em.add_field(name='You have no pending Admin Help requests.', value='To submit a request please use `admin new <message>`', 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:
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] == 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 {ctx.guild.get_member(request[1]).display_name} 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(f'select prefix from guild_config where guild_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='012214819999548252842:tarnolfyw44').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('128GnQPOx0u7oPGvu5nAJks_Qv2R0Ru9ILAniICoxhJ8')
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))