Compare commits

..
Author SHA1 Message Date
Dusty.P c1a19ce2c8 Create LICENSE 2018-05-10 12:47:21 -08:00
36 changed files with 639 additions and 1356 deletions
Binary file not shown.
+1 -90
View File
@@ -1,93 +1,4 @@
bot_secrets.json
google_client_secret.json
logs/*
*.sh.swp
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# mypy
.mypy_cache/
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/dictionaries
.idea/**/shelf
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# CMake
cmake-build-debug/
cmake-build-release/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
logs/*
+4 -1
View File
@@ -4,8 +4,11 @@
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="R User Library" level="project" />
<orderEntry type="library" name="R Skeletons" level="application" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
<option name="projectConfiguration" value="py.test" />
<option name="PROJECT_TEST_RUNNER" value="py.test" />
</component>
</module>
-5
View File
@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="Dustin.Pianalto">
<words>
<w>rcon</w>
</words>
</dictionary>
</component>
+1 -1
View File
@@ -3,5 +3,5 @@
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (Geeksbot)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Dusty.P
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
View File
+78 -103
View File
@@ -4,10 +4,11 @@ import json
import logging
import inspect
import os
from src.imports import checks, utils
from .imports import checks
config_dir = 'src/config/'
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'
@@ -51,7 +52,6 @@ class Admin:
await ctx.send('Geeksbot is restarting.')
with open(f'{config_dir}reboot', 'w') as f:
f.write(f'1\n{ctx.channel.id}')
# noinspection PyProtectedMember
os._exit(1)
@commands.command(hidden=True)
@@ -72,19 +72,20 @@ class Admin:
emoji_code = f'<a:{emoji.name}:{emoji.id}>'
else:
emoji_code = f'<:{emoji.name}:{emoji.id}>'
if await self.bot.db_con.fetch('select id from geeksbot_emojis where id = $1', emoji.id):
await self.bot.db_con.execute("update geeksbot_emojis set id = $2, name = $1, code = $3 "
"where name = $1", emoji.name, emoji.id, emoji_code)
if self.bot.con.all('select id from geeksbot_emojis where id = %(id)s', {'id': emoji.id}):
self.bot.con.run("update geeksbot_emojis set id = %(id)s, name = %(name)s, code = %(emoji_code)s "
"where name = %(name)s",
{'name': emoji.name, 'id': emoji.id, 'emoji_code': emoji_code})
else:
await self.bot.db_con.execute("insert into geeksbot_emojis(id,name,code) values ($2,$1,$3)",
emoji.name, emoji.id, emoji_code)
self.bot.con.run("insert into geeksbot_emojis(id,name,code) values (%(id)s,%(name)s,%(emoji_code)s)",
{'name': emoji.name, 'id': emoji.id, 'emoji_code': emoji_code})
await ctx.message.add_reaction('')
await ctx.send(f'Emojis have been updated in the database.')
@commands.command(hidden=True)
@commands.check(checks.is_guild_owner)
async def get_guild_config(self, ctx):
config = await self.bot.db_con.fetchrow('select * from guild_config where guild_id = $1', ctx.guild.id)
config = self.bot.con.one('select * from guild_config where guild_id = %(id)s', {'id': ctx.guild.id})
configs = [str(config)[i:i+1990] for i in range(0, len(config), 1990)]
await ctx.message.author.send(f'The current config for the {ctx.guild.name} guild is:\n')
admin_log.info(configs)
@@ -94,49 +95,45 @@ class Admin:
@commands.group(case_insensitive=True)
async def set(self, ctx):
"""Group for setting configuration options"""
"""Run help set for more info"""
pass
@commands.group(case_insensitive=True)
async def add(self, ctx):
"""Group for adding items to guild config"""
"""Run help set for more info"""
pass
@commands.group(case_insensitive=True)
async def remove(self, ctx):
"""Group for removing items from guild config"""
"""Run help set for more info"""
pass
@set.command(name='admin_chan', aliases=['ac', 'admin_chat', 'admin chat'])
async def _admin_channel(self, ctx, channel: discord.TextChannel=None):
"""Sets the admin notification channel"""
if ctx.guild:
if await checks.is_admin(self.bot, ctx):
if checks.is_admin(self.bot, ctx):
if channel is not None:
await self.bot.db_con.execute('update guild_config set admin_chat = $2 where guild_id = $1',
ctx.guild.id, channel.id)
self.bot.con.run('update guild_config set admin_chat = %(chan)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'chan': channel.id})
await ctx.send(f'{channel.name} is now set as the Admin Chat channel for this guild.')
@set.command(name='channel_lockdown', aliases=['lockdown', 'restrict_access', 'cl'])
async def _channel_lockdown(self, ctx, config='true'):
"""Toggles the channel lockdown restriction
When this is active Geeksbot can only respond in channels defined in allowed_channels
If you run this before configuring allowed_channels it will tell you to run that command first."""
if ctx.guild:
if await checks.is_admin(self.bot, ctx):
if checks.is_admin(self.bot, ctx):
if str(config).lower() == 'true':
if await self.bot.db_con.fetchval('select allowed_channels from guild_config '
'where guild_id = $1', ctx.guild.id) is []:
if self.bot.con.one('select allowed_channels from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}) is []:
await ctx.send('Please set at least one allowed channel before running this command.')
else:
await self.bot.db_con.execute('update guild_config set channel_lockdown = True '
'where guild_id = $1', ctx.guild.id)
self.bot.con.run('update guild_config set channel_lockdown = True where guild_id = %(id)s',
{'id': ctx.guild.id})
await ctx.send('Channel Lockdown is now active.')
elif str(config).lower() == 'false':
if await self.bot.db_con.fetchval('select channel_lockdown from guild_config where guild_id = $1',
ctx.guild.id):
await self.bot.db_con.execute('update guild_config set channel_lockdown = False '
'where guild_id = $1', ctx.guild.id)
if self.bot.con.one('select channel_lockdown from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}):
self.bot.con.run('update guild_config set channel_lockdown = False where guild_id = %(id)s',
{'id': ctx.guild.id})
await ctx.send('Channel Lockdown has been deactivated.')
else:
await ctx.send('Channel Lockdown is already deactivated.')
@@ -147,74 +144,59 @@ class Admin:
@add.command(name='allowed_channels', aliases=['channel', 'ac'])
async def _allowed_channels(self, ctx, *, channels):
"""Defines channels Geeksbot can respond in
This only takes effect if channel_lockdown is enabled.
If one of the channels passed is not found then it is ignored."""
if ctx.guild:
if await checks.is_admin(self.bot, ctx):
if checks.is_admin(self.bot, ctx):
channels = channels.lower().replace(' ', '').split(',')
existing_channels = list()
channels_add = list()
admin_log.info(channels)
allowed_channels = await self.bot.db_con.fetchval('select allowed_channels from guild_config '
'where guild_id = $1', ctx.guild.id)
if allowed_channels == 'null':
allowed_channels = None
channels = [discord.utils.get(ctx.guild.channels, name=channel)
for channel in channels if channel is not None]
if allowed_channels and channels:
allowed_channels = [int(channel) for channel in json.loads(allowed_channels)]
existing_channels = [channel for channel in channels if channel.id in allowed_channels]
channels_add = [channel for channel in channels if channel.id not in allowed_channels]
allowed_channels += [channel.id for channel in channels if channel.id not in allowed_channels]
await self.bot.db_con.execute('update guild_config set allowed_channels = $2 where guild_id = $1',
ctx.guild.id, json.dumps(allowed_channels))
elif channels:
admin_log.info('Config is empty')
allowed_channels = [channel.id for channel in channels]
await self.bot.db_con.execute('update guild_config set allowed_channels = $2 '
'where guild_id = $1', ctx.guild.id,
json.dumps(allowed_channels))
else:
await ctx.send('None of those are valid text channels for this guild.')
return
if existing_channels:
channel_str = '\n'.join([str(channel.name) for channel in existing_channels])
await ctx.send(f'The following channels were skipped because they are already in the config:\n'
f'{channel_str}\n')
if channels_add:
channel_str = '\n'.join([str(channel.name) for channel in channels_add])
await ctx.send('The following channels have been added to the allowed channel list:\n'
f'{channel_str}\n')
added = ''
for channel in channels:
chnl = discord.utils.get(ctx.guild.channels, name=channel)
if chnl is None:
await ctx.send(f'{channel} is not a valid text channel in this guild.')
else:
admin_log.info('Chan found')
if self.bot.con.one('select allowed_channels from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}):
if chnl.id in json.loads(self.bot.con.one('select allowed_channels from guild_config '
'where guild_id = %(id)s',
{'id': ctx.guild.id})):
admin_log.info('Chan found in config')
await ctx.send(f'{channel} is already in the list of allowed channels. Skipping...')
else:
admin_log.info('Chan not found in config')
allowed_channels = json.loads(self.bot.con.one('select allowed_channels from '
'guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})).append(chnl.id)
self.bot.con.run('update guild_config set allowed_channels = %(channels)s '
'where guild_id = %(id)s',
{'id': ctx.guild.id, 'channels': allowed_channels})
added = f'{added}\n{channel}'
else:
admin_log.info('Chan not found in config')
allowed_channels = [chnl.id]
self.bot.con.run('update guild_config set allowed_channels = %(channels)s '
'where guild_id = %(id)s',
{'id': ctx.guild.id, 'channels': allowed_channels})
added = f'{added}\n{channel}'
if added != '':
await ctx.send(f'The following channels have been added to the allowed channel list: {added}')
await ctx.message.add_reaction('')
else:
await ctx.send(f'You are not authorized to run this command.')
else:
await ctx.send('This command must be run from inside a guild.')
# TODO Fix view_code
@commands.command(hidden=True)
@commands.command()
@commands.is_owner()
async def view_code(self, ctx, code_name):
pag = utils.Paginator(self.bot, prefix='```py', suffix='```')
pag.add(inspect.getsource(self.bot.all_commands[code_name].callback))
for page in pag.pages():
await ctx.send(page)
await ctx.send(f"```py\n{inspect.getsource(self.bot.get_command(code_name).callback)}\n```")
@add.command(aliases=['prefix', 'p'])
@commands.cooldown(1, 5, type=commands.BucketType.guild)
async def add_prefix(self, ctx, *, prefix=None):
"""Adds a custom prefix for the current guild
Note: This overwrites the default of g$. If you would
like to keep using g$ you will need to add it to the
Guild config as well."""
if ctx.guild:
if await checks.is_admin(self.bot, ctx):
prefixes = await self.bot.db_con.fetchval('select prefix from guild_config where guild_id = $1',
ctx.guild.id)
if checks.is_admin(self.bot, ctx):
prefixes = self.bot.con.one('select prefix from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if prefix is None:
await ctx.send(prefixes)
return
@@ -226,8 +208,8 @@ class Admin:
if len(prefixes) > 10:
await ctx.send(f'Only 10 prefixes are allowed per guild.\nPlease remove some before adding more.')
prefixes = prefixes[:10]
await self.bot.db_con.execute('update guild_config set prefix = $2 where guild_id = $1',
ctx.guild.id, prefixes)
self.bot.con.run('update guild_config set prefix = %(prefixes)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'prefixes': prefixes})
await ctx.guild.me.edit(nick=f'[{prefixes[0]}] Geeksbot')
await ctx.send(f"Updated. You currently have {len(prefixes)} "
f"{'prefix' if len(prefixes) == 1 else 'prefixes'} "
@@ -240,13 +222,10 @@ class Admin:
@remove.command(aliases=['prefix', 'p'])
@commands.cooldown(1, 5, type=commands.BucketType.guild)
async def remove_prefix(self, ctx, *, prefix=None):
"""Removes custom prefix from the current guild
If the last prefix is removed then Geeksbot will default
Back to g$"""
if ctx.guild:
if await checks.is_admin(self.bot, ctx):
prefixes = await self.bot.db_con.fetchval('select prefix from guild_config where guild_id = $1',
ctx.guild.id)
if checks.is_admin(self.bot, ctx):
prefixes = self.bot.con.one('select prefix from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
found = 0
if prefix is None:
await ctx.send(prefixes)
@@ -263,8 +242,8 @@ class Admin:
else:
await ctx.send(f'The prefix {p} is not in the config for this guild.')
if found:
await self.bot.db_con.execute('update guild_config set prefix = $2 where guild_id = $1',
ctx.guild.id, prefixes)
self.bot.con.run('update guild_config set prefix = %(prefixes)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'prefixes': prefixes})
await ctx.guild.me.edit(nick=f'[{prefixes[0] if len(prefixes) != 0 else self.bot.default_prefix}] '
f'Geeksbot')
await ctx.send(f"Updated. You currently have {len(prefixes)} "
@@ -279,19 +258,16 @@ class Admin:
@commands.cooldown(1, 5, type=commands.BucketType.guild)
@commands.check(checks.is_guild_owner)
async def _add_admin_role(self, ctx, role=None):
"""Adds role to the admin list for current guild
Allowing members of that role to run admin commands
on the current guild."""
role = discord.utils.get(ctx.guild.roles, name=role)
if role is not None:
roles = json.loads(await self.bot.db_con.fetchval('select admin_roles from guild_config '
'where guild_id = $1', ctx.guild.id))
roles = json.loads(self.bot.con.one('select admin_roles from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}))
if role.name in roles:
await ctx.send(f'{role.name} is already registered as an admin role in this guild.')
else:
roles[role.name] = role.id
await self.bot.db_con.execute('update guild_config set admin_roles = $2 where guild_id = $1',
ctx.guild.id, json.dumps(roles))
self.bot.con.run('update guild_config set admin_roles = %(roles)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'roles': json.dumps(roles)})
await ctx.send(f'{role.name} has been added to the list of admin roles for this guild.')
else:
await ctx.send('You must include a role with this command.')
@@ -300,15 +276,14 @@ class Admin:
@commands.cooldown(1, 5, type=commands.BucketType.guild)
@commands.check(checks.is_guild_owner)
async def _remove_admin_role(self, ctx, role=None):
"""Removes role from admin list in current guild"""
role = discord.utils.get(ctx.guild.roles, name=role)
if role is not None:
roles = json.loads(await self.bot.db_con.fetchval('select admin_roles from guild_config '
'where guild_id = $1', ctx.guild.id))
roles = json.loads(self.bot.con.one('select admin_roles from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}))
if role.name in roles:
del roles[role.name]
await self.bot.db_con.execute('update guild_config set admin_roles = $2 where guild_id = $1',
ctx.guild.id, json.dumps(roles))
self.bot.con.run('update guild_config set admin_roles = %(roles)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'roles': roles})
await ctx.send(f'{role.name} has been removed from the list of admin roles for this guild.')
else:
await ctx.send(f'{role.name} is not registered as an admin role in this guild.')
+107 -90
View File
@@ -1,10 +1,9 @@
import discord
from discord.ext import commands
import logging
from datetime import datetime
import json
import re
from src.imports import utils
from .imports import utils
config_dir = 'config/'
admin_id_file = 'admin_ids'
@@ -51,20 +50,17 @@ class BotEvents:
config_str = f'{config_str}\n{" "*4}{config}: {guild_config[config]}'
return config_str
# noinspection PyUnusedLocal
async def on_raw_message_delete(self, msg_id, chan_id):
await self.bot.db_con.execute('update messages set deleted_at = $1 where id = $2',
datetime.utcnow(), msg_id)
self.bot.con.run('update messages set deleted_at = %(time)s where id = %(id)s',
{'time': datetime.utcnow(), 'id': msg_id})
# noinspection PyUnusedLocal
async def on_raw_bulk_message_delete(self, msg_ids, chan_id):
del_time = datetime.utcnow()
sql = ''
for msg_id in msg_ids:
await self.bot.db_con.execute('update messages set deleted_at = $1 where id = $2',
del_time, msg_id)
sql += f';update messages set deleted_at = %(time)s where id = {msg_id}'
self.bot.con.run(sql, {'time': datetime.utcnow()})
async def on_message(self, ctx):
# noinspection PyBroadException
try:
if ctx.author in self.bot.infected:
if datetime.now().timestamp() > self.bot.infected[ctx.author][1] + 300:
@@ -77,22 +73,36 @@ class BotEvents:
sql = 'insert into messages (id, tts, type, content, embeds, channel, mention_everyone, mentions,\
channel_mentions, role_mentions, webhook, attachments, pinned, reactions, guild, created_at,\
system_content, author) \
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)'
msg_data = [ctx.id, ctx.tts, str(ctx.type), ctx.content, [json.dumps(e.to_dict()) for e in ctx.embeds],
ctx.channel.id, ctx.mention_everyone, [user.id for user in ctx.mentions],
[channel.id for channel in ctx.channel_mentions], [role.id for role in ctx.role_mentions],
ctx.webhook_id, [json.dumps({'id': a.id, 'size': a.size, 'height': a.height, 'width': a.width,
'filename': a.filename, 'url': a.url}) for a in ctx.attachments],
ctx.pinned, [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions],
ctx.guild.id if ctx.guild else ctx.author.id, ctx.created_at, ctx.system_content, ctx.author.id]
await self.bot.db_con.execute(sql, *msg_data)
values (%(id)s, %(tts)s, %(type)s, %(content)s, %(embeds)s, %(channel)s, %(mention_everyone)s, %(mentions)s,\
%(channel_mentions)s, %(role_mentions)s, %(webhook)s, %(attachments)s, %(pinned)s, %(reactions)s, %(guild)s,\
%(created_at)s, %(system_content)s, %(author)s)'
msg_data = dict()
msg_data['id'] = ctx.id
msg_data['tts'] = ctx.tts
msg_data['type'] = str(ctx.type)
msg_data['content'] = ctx.content
msg_data['embeds'] = [json.dumps(e.to_dict()) for e in ctx.embeds]
msg_data['channel'] = ctx.channel.id
msg_data['mention_everyone'] = ctx.mention_everyone
msg_data['mentions'] = [user.id for user in ctx.mentions]
msg_data['channel_mentions'] = [channel.id for channel in ctx.channel_mentions]
msg_data['role_mentions'] = [role.id for role in ctx.role_mentions]
msg_data['webhook'] = ctx.webhook_id
msg_data['attachments'] = [json.dumps({'id': a.id, 'size': a.size, 'height': a.height, 'width': a.width,
'filename': a.filename, 'url': a.url}) for a in ctx.attachments]
msg_data['pinned'] = ctx.pinned
msg_data['guild'] = ctx.guild.id
msg_data['created_at'] = ctx.created_at
msg_data['system_content'] = ctx.system_content
msg_data['author'] = ctx.author.id
msg_data['reactions'] = [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions]
self.bot.con.run(sql, msg_data)
if ctx.guild:
if ctx.author != ctx.guild.me:
if await self.bot.db_con.fetchval("select pg_filter from guild_config where guild_id = $1",
ctx.guild.id):
if self.bot.con.one(f"select pg_filter from guild_config where guild_id = {ctx.guild.id}"):
profane = 0
for word in await self.bot.db_con.fetchval('select profane_words from guild_config '
'where guild_id = $1', ctx.guild.id):
for word in self.bot.con.one('select profane_words from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id}):
word = word.strip()
if word in ctx.content.lower():
events_log.info(f'Found non PG word {word}')
@@ -119,44 +129,57 @@ class BotEvents:
await react.message.channel.send(f"You can't Poop on me {user.mention} :P")
reactions = react.message.reactions
reacts = [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in reactions]
await self.bot.db_con.execute('update messages set reactions = $2 where id = $1',
react.message.id, reacts)
self.bot.con.run('update messages set reactions = %(reacts)s where id = %(id)s',
{'id': react.message.id, 'reacts': reacts})
async def on_message_edit(self, before, ctx):
previous_content = await self.bot.db_con.fetchval('select previous_content from messages where id = $1', ctx.id)
previous_content = self.bot.con.one('select previous_content from messages where id = %(id)s', {'id': ctx.id})
if previous_content:
previous_content.append(before.content)
else:
previous_content = [before.content]
previous_embeds = await self.bot.db_con.fetchval('select previous_embeds from messages where id = $1', ctx.id)
previous_embeds = self.bot.con.one('select previous_embeds from messages where id = %(id)s', {'id': ctx.id})
if previous_embeds:
previous_embeds.append([json.dumps(e.to_dict()) for e in before.embeds])
else:
previous_embeds = [[json.dumps(e.to_dict()) for e in before.embeds]]
sql = 'update messages set (edited_at, previous_content, previous_embeds, tts, type, content, embeds, ' \
'channel, mention_everyone, mentions, channel_mentions, role_mentions, webhook, attachments, pinned, ' \
'reactions, guild, created_at, system_content, author) = ' \
'($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)' \
'where id = $21'
msg_data = [datetime.utcnow(), previous_content, previous_embeds, ctx.tts, str(ctx.type), ctx.content,
[json.dumps(e.to_dict()) for e in ctx.embeds], ctx.channel.id, ctx.mention_everyone,
[user.id for user in ctx.mentions], [channel.id for channel in ctx.channel_mentions],
[role.id for role in ctx.role_mentions], ctx.webhook_id,
[json.dumps({'id': a.id, 'size': a.size, 'height': a.height, 'width': a.width,
'filename': a.filename, 'url': a.url}) for a in ctx.attachments], ctx.pinned,
[json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions], ctx.guild.id,
ctx.created_at, ctx.system_content, ctx.author.id, ctx.id]
await self.bot.db_con.execute(sql, *msg_data)
sql = 'update messages set (edited_at, previous_content, previous_embeds, tts, type, content,\
embeds, channel, mention_everyone, mentions, channel_mentions, role_mentions, webhook,\
attachments, pinned, reactions, guild, created_at, system_content, author) \
= (%(edited_at)s, %(previous_content)s, %(previous_embeds)s, %(tts)s, %(type)s, %(content)s,\
%(embeds)s, %(channel)s, %(mention_everyone)s, %(mentions)s, %(channel_mentions)s, %(role_mentions)s,\
%(webhook)s, %(attachments)s, %(pinned)s, %(reactions)s, %(guild)s, %(created_at)s, %(system_content)s,\
%(author)s) where id = %(id)s'
msg_data = dict()
msg_data['id'] = ctx.id
msg_data['tts'] = ctx.tts
msg_data['type'] = str(ctx.type)
msg_data['content'] = ctx.content
msg_data['embeds'] = [json.dumps(e.to_dict()) for e in ctx.embeds]
msg_data['channel'] = ctx.channel.id
msg_data['mention_everyone'] = ctx.mention_everyone
msg_data['mentions'] = [user.id for user in ctx.mentions]
msg_data['channel_mentions'] = [channel.id for channel in ctx.channel_mentions]
msg_data['role_mentions'] = [role.id for role in ctx.role_mentions]
msg_data['webhook'] = ctx.webhook_id
msg_data['attachments'] = ctx.attachments
msg_data['pinned'] = ctx.pinned
msg_data['guild'] = ctx.guild.id
msg_data['created_at'] = ctx.created_at
msg_data['system_content'] = ctx.system_content
msg_data['author'] = ctx.author.id
msg_data['reactions'] = [json.dumps({'emoji': r.emoji, 'count': r.count}) for r in ctx.reactions]
msg_data['previous_content'] = previous_content
msg_data['previous_embeds'] = previous_embeds
msg_data['edited_at'] = datetime.utcnow()
self.bot.con.run(sql, msg_data)
# noinspection PyMethodMayBeStatic
async def on_command_error(self, ctx, error):
pag = utils.Paginator(ctx.bot, embed=True, max_line_length=48)
pag.set_embed_meta(title=f'Command Error',
color=self.bot.error_color,
thumbnail=f'{ctx.guild.me.avatar_url}')
pag.add(error)
book = utils.Book(pag, (None, ctx.channel, self.bot, ctx.message))
await book.create_book()
@staticmethod
async def on_command_error(ctx, error):
if ctx.channel.id == 418452585683484680 and type(error) == discord.ext.commands.errors.CommandNotFound:
return
for page in utils.paginate(error):
await ctx.send(page)
async def on_guild_join(self, guild):
with open(f"{config_dir}{default_guild_config_file}", 'r') as file:
@@ -166,25 +189,31 @@ class BotEvents:
default_config['name'] = guild.name.replace("'", "\\'")
default_config['guild_id'] = guild.id
events_log.info(default_config)
await self.bot.db_con.execute("insert into guild_config(guild_id, guild_name, admin_roles, rcon_enabled, "
"channel_lockdown, raid_status, pg_filter, patreon_enabled, referral_enabled) "
"values ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
default_config['guild_id'], default_config['name'],
json.dumps(default_config['admin_roles']), default_config['rcon_enabled'],
default_config['channel_lockdown'], default_config['raid_status'],
default_config['pg_filter'], default_config['patreon_enabled'],
default_config['referral_enabled'])
self.bot.con.run("insert into guild_config(guild_id, guild_name, admin_roles, rcon_enabled, channel_lockdown,\
raid_status, pg_filter, patreon_enabled, referral_enabled)\
values (%(guild_id)s, %(name)s, %(admin_roles)s, %(rcon_enabled)s, %(channel_lockdown)s,\
%(raid_status)s, %(pg_filter)s, %(patreon_enabled)s, %(referral_enabled)s)",
{'guild_id': default_config['guild_id'],
'name': default_config['name'],
'admin_roles': json.dumps(default_config['admin_roles']),
'rcon_enabled': default_config['rcon_enabled'],
'channel_lockdown': default_config['channel_lockdown'],
'raid_status': default_config['raid_status'],
'pg_filter': default_config['pg_filter'],
'patreon_enabled': default_config['patreon_enabled'],
'referral_enabled': default_config['referral_enabled']
})
events_log.info(f'Entry Created for {guild.name}')
await guild.me.edit(nick='[g$] Geeksbot')
async def on_guild_remove(self, guild):
await self.bot.db_con.execute(f'delete from guild_config where guild_id = $1', guild.id)
self.bot.con.run(f'delete from guild_config where guild_id = %(id)s', {'id': guild.id})
events_log.info(f'Left the {guild.name} guild.')
async def on_member_join(self, member):
events_log.info(f'Member joined: {member.name} {member.id} Guild: {member.guild.name} {member.guild.id}')
join_chan = await self.bot.db_con.fetchval('select join_leave_chat from guild_config where guild_id = $1',
member.guild.id)
join_chan = self.bot.con.one('select join_leave_chat from guild_config where guild_id = %(id)s',
{'id': member.guild.id})
if join_chan:
em = discord.Embed(style='rich',
color=embed_color
@@ -197,30 +226,31 @@ class BotEvents:
em.set_footer(text=f"{member.guild.name} | {member.joined_at.strftime('%Y-%m-%d at %H:%M:%S GMT')}",
icon_url=member.guild.icon_url)
await discord.utils.get(member.guild.channels, id=join_chan).send(embed=em)
mem_data = [member.id,
member.name,
member.discriminator,
member.bot
]
mem = await self.bot.db_con.fetchval('select guilds,nicks from user_data where id = $1', member.id)
mem_data = {'id': member.id,
'name': member.name,
'discriminator': member.discriminator,
'bot': member.bot
}
mem = self.bot.con.one('select guilds,nicks from user_data where id = %(id)s', {'id': member.id})
if mem:
mem[1].append(json.dumps({member.guild.id: member.display_name}))
mem[0].append(member.guild.id)
mem_data.append(mem[1])
mem_data.append(mem[0])
self.bot.con.run('update user_data set (name, discriminator, bot, nicks, guilds) = '
'($2, $3, $4, $5, $6) where id = $1', *mem_data)
mem_data['nicks'] = mem[1]
mem_data['guilds'] = mem[0]
self.bot.con.run('update user_data set (name, discriminator, bot, nicks, guilds) =\
(%(name)s, %(discriminator)s, %(bot)s, %(nicks)s, %(guilds)s) where\
id = %(id)s', mem_data)
else:
mem_data.append([json.dumps({member.guild.id: member.display_name})])
mem_data.append([member.guild.id])
self.bot.con.run('insert into user_data (id, name, discriminator, bot, nicks, guilds) '
'values ($1, $2, $3, $4, $5, $6)', *mem_data)
mem_data['nicks'] = [json.dumps({member.guild.id: member.display_name})]
mem_data['guilds'] = [member.guild.id]
self.bot.con.run('insert into user_data (id, name, discriminator, bot, nicks, guilds) values\
(%(id)s, %(name)s, %(discriminator)s, %(bot)s, %(nicks)s, %(guilds)s)', mem_data)
async def on_member_remove(self, member):
leave_time = datetime.utcnow()
events_log.info(f'Member left: {member.name} {member.id} Guild: {member.guild.name} {member.guild.id}')
join_chan = await self.bot.db_con.fetchval('select join_leave_chat from guild_config where guild_id = $1',
member.guild.id)
join_chan = self.bot.con.one('select join_leave_chat from guild_config where guild_id = %(id)s',
{'id': member.guild.id})
if join_chan:
em = discord.Embed(style='rich',
color=red_color
@@ -243,19 +273,6 @@ class BotEvents:
em.set_footer(text=f"{member.guild.name} | {datetime.utcnow().strftime('%Y-%m-%d at %H:%M:%S GMT')}",
icon_url=member.guild.icon_url)
await discord.utils.get(member.guild.channels, id=join_chan).send(embed=em)
mem_data = [member.id,
member.name,
member.discriminator,
member.bot
]
mem = await self.bot.db_con.fetchrow('select guilds,nicks from user_data where id = $1', member.id)
if mem:
mem[0].remove(member.guild.id)
mem[1].remove(json.dumps({member.guild.id: member.display_name}))
mem_data.append(mem[1])
mem_data.append(mem[0])
self.bot.con.run('update user_data set (name, discriminator, bot, nicks, guilds) = '
'($2, $3, $4, $5, $6) where id = $1', *mem_data)
def setup(bot):
+19 -43
View File
@@ -33,9 +33,6 @@ class Fun:
@commands.command()
@commands.cooldown(1, 30, type=commands.BucketType.user)
async def infect(self, ctx, member: discord.Member, emoji):
"""Infects a user with the given emoji
Every time the user sends a message that I am also in I will react to that message with said emoji."""
if member.id == self.bot.user.id and ctx.author.id != owner_id:
await ctx.send(f'You rolled a Critical Fail...\nInfection bounces off and rebounds on the attacker.')
member = ctx.author
@@ -51,7 +48,6 @@ class Fun:
@commands.command()
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def heal(self, ctx, member: discord.Member):
"""Removes infection from user."""
if ctx.author == member and ctx.author.id != owner_id:
await ctx.send('You can\'t heal yourself silly...')
else:
@@ -61,7 +57,7 @@ class Fun:
else:
await ctx.send(f'{member.display_name} is not infected...')
@commands.command(hidden=True)
@commands.command()
@commands.is_owner()
async def print_infections(self, ctx):
await ctx.author.send(f'```{self.bot.infected}```')
@@ -69,15 +65,13 @@ class Fun:
@commands.command()
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def slap(self, ctx, member: discord.Member):
"""IRC Style Trout Slap"""
trout = await self.bot.db_con.fetchval("select code from geeksbot_emojis where id = 449083238766477312")
if member.id == self.bot.user.id and ctx.author.id != owner_id:
await ctx.send(f'You rolled a Critical Fail...\nThe trout bounces off and rebounds on the attacker.')
await ctx.send(f'{ctx.author.mention} '
f'You slap yourself in the face with a large trout {trout}')
f'You slap yourself in the face with a large trout <:trout:408543365085397013>')
else:
await ctx.send(f'{ctx.author.display_name} slaps '
f'{member.mention} around a bit with a large trout {trout}')
f'{member.mention} around a bit with a large trout <:trout:408543365085397013>')
@staticmethod
def get_factorial(number):
@@ -86,23 +80,22 @@ class Fun:
a = a * (i + 1)
return a
@commands.command()
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def fact(self, ctx, number: int):
"""Returns the given factorial up to 20,000!"""
if 0 < number < 20001:
n = 1990
with ctx.channel.typing():
a = await self.bot.loop.run_in_executor(None, self.get_factorial, number)
if len(str(a)) > 6000:
for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
await ctx.author.send(f'```py\n{b}```')
await ctx.send(f"{ctx.author.mention} Check your DMs.")
else:
for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
await ctx.send(f'```py\n{b}```')
else:
await ctx.send("Invalid number. Please enter a number between 0 and 20,000")
# @commands.command()
# @commands.cooldown(1, 5, type=commands.BucketType.user)
# async def fact(self, ctx, number:int):
# if number < 20001 and number > 0:
# n = 1990
# with ctx.channel.typing():
# a = await self.bot.loop.run_in_executor(None, self.get_factorial, number)
# if len(str(a)) > 6000:
# for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
# await ctx.author.send(f'```py\n{b}```')
# await ctx.send(f"{ctx.author.mention} Check your DMs.")
# else:
# for b in [str(a)[i:i+n] for i in range(0, len(str(a)), n)]:
# await ctx.send(f'```py\n{b}```')
# else:
# await ctx.send("Invalid number. Please enter a number between 0 and 20,000")
@commands.command(hidden=True)
@commands.is_owner()
@@ -148,28 +141,11 @@ class Fun:
else:
await ctx.send('Not connected to that voice channel.')
# noinspection PyUnusedLocal
@commands.command(hidden=True)
@commands.is_owner()
async def volume(self, ctx, volume: float):
self.bot.player.volume = volume
@commands.command(name='explode', aliases=['splode'])
async def explode_user(self, ctx, member: discord.Member=None):
"""Trolls user by punching them to oblivion."""
if member is None or member.id == 396588996706304010:
member = ctx.author
trans = await self.bot.db_con.fetchval('select code from geeksbot_emojis where id = 405943174809255956')
msg = await ctx.send(f'{member.mention}{trans*20}{self.bot.unicode_emojis["left_fist"]}')
for i in range(4):
await asyncio.sleep(0.5)
await msg.edit(content=f'{member.mention}{trans*(20-(i*5))}{self.bot.unicode_emojis["left_fist"]}')
await asyncio.sleep(0.1)
await msg.edit(content=f'{self.bot.unicode_emojis["boom"]}')
await asyncio.sleep(0.5)
await msg.edit(content=f'{self.bot.unicode_emojis["boom"]} <---- {member.mention} that was you...')
def setup(bot):
bot.add_cog(Fun(bot))
+54
View File
@@ -0,0 +1,54 @@
import discord
from discord.ext import commands
import logging
from .imports.utils import paginate, run_command
import asyncio
owner_id = 351794468870946827
embed_color = discord.Colour.from_rgb(49, 107, 111)
git_log = logging.getLogger('git')
class Git:
def __init__(self, bot):
self.bot = bot
@commands.group(case_insensitive=True)
async def git(self, ctx):
"""Run help git for more info"""
pass
@git.command()
@commands.is_owner()
async def pull(self, ctx):
em = discord.Embed(style='rich',
title=f'Git Pull',
color=embed_color)
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
result = await asyncio.wait_for(self.bot.loop.create_task(run_command('git fetch --all')), 120) + '\n'
result += await asyncio.wait_for(self.bot.loop.create_task(run_command('git reset --hard '
'origin/master')), 120) + '\n\n'
result += await asyncio.wait_for(self.bot.loop.create_task(run_command('git show --stat | '
'sed "s/.*@.*[.].*/ /g"')), 10)
results = paginate(result, maxlen=1014)
for page in results[:5]:
em.add_field(name='', value=f'{page}')
await ctx.send(embed=em)
@git.command()
@commands.is_owner()
async def status(self, ctx):
em = discord.Embed(style='rich',
title=f'Git Pull',
color=embed_color)
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
result = await asyncio.wait_for(self.bot.loop.create_task(run_command('git status')), 10)
results = paginate(result, maxlen=1014)
for page in results[:5]:
em.add_field(name='', value=f'{page}')
await ctx.send(embed=em)
def setup(bot):
bot.add_cog(Git(bot))
@@ -1,46 +1,46 @@
import discord
import json
from src.imports import utils
from . import utils
owner_id = 351794468870946827
async def check_admin_role(bot, ctx, member):
admin_roles = json.loads(await bot.db_con.fetchval(f"select admin_roles from guild_config where guild_id = $1",
ctx.guild.id))
def check_admin_role(bot, ctx, member):
admin_roles = json.loads(bot.con.one(f"select admin_roles from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id}))
for role in admin_roles:
if discord.utils.get(ctx.guild.roles, id=admin_roles[role]) in member.roles:
return True
return member.id == ctx.guild.owner.id or member.id == owner_id
async def check_rcon_role(bot, ctx, member):
rcon_admin_roles = json.loads(await bot.db_con.fetchval("select rcon_admin_roles from guild_config "
"where guild_id = $1", ctx.guild.id))
def check_rcon_role(bot, ctx, member):
rcon_admin_roles = json.loads(bot.con.one("select rcon_admin_roles from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id}))
for role in rcon_admin_roles:
if discord.utils.get(ctx.guild.roles, id=rcon_admin_roles[role]) in member.roles:
return True
return member.id == ctx.guild.owner.id or member.id == owner_id
async def is_admin(bot, ctx):
admin_roles = json.loads(await bot.db_con.fetchval("select admin_roles from guild_config where guild_id = $1",
ctx.guild.id))
def is_admin(bot, ctx):
admin_roles = json.loads(bot.con.one("select admin_roles from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id}))
for role in admin_roles:
if discord.utils.get(ctx.guild.roles, id=admin_roles[role]) in ctx.message.author.roles:
return True
return ctx.message.author.id == ctx.guild.owner.id or ctx.message.author.id == owner_id
async def is_guild_owner(ctx):
def is_guild_owner(ctx):
if ctx.guild:
return ctx.message.author.id == ctx.guild.owner.id or ctx.message.author.id == owner_id
return False
async def is_rcon_admin(bot, ctx):
rcon_admin_roles = json.loads(await bot.db_con.fetchval("select rcon_admin_roles from guild_config "
"where guild_id = $1", ctx.guild.id))
def is_rcon_admin(bot, ctx):
rcon_admin_roles = json.loads(bot.con.one("select rcon_admin_roles from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id}))
for role in rcon_admin_roles:
if discord.utils.get(ctx.guild.roles, id=rcon_admin_roles[role]) in ctx.message.author.roles:
return True
+93
View File
@@ -0,0 +1,93 @@
from io import StringIO
import sys
import asyncio
import discord
from discord.ext.commands.formatter import Paginator
from . import checks
class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stdout = self._stdout
async def mute(bot, ctx, admin=0, member_id=None):
mute_role = bot.con.one(f'select muted_role from guild_config where guild_id = {ctx.guild.id}')
if mute_role:
if admin or checks.is_admin(bot, ctx):
if ctx.guild.me.guild_permissions.manage_roles:
if member_id:
ctx.guild.get_member(member_id).edit(roles=[discord.utils.get(ctx.guild.roles, id=mute_role)])
def to_list_of_str(items, out: list=list(), level=1, recurse=0):
def rec_loop(item, key, out, level):
quote = '"'
if type(item) == list:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}[')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}]')
elif type(item) == dict:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{{')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}}}')
else:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{repr(item)},')
if type(items) == list:
if not recurse:
out = list()
out.append('[')
for item in items:
rec_loop(item, None, out, level)
if not recurse:
out.append(']')
elif type(items) == dict:
if not recurse:
out = list()
out.append('{')
for key in items:
rec_loop(items[key], key, out, level)
if not recurse:
out.append('}')
return out
def paginate(text, maxlen=1990):
paginator = Paginator(prefix='```py', max_size=maxlen+10)
if type(text) == list:
data = to_list_of_str(text)
elif type(text) == dict:
data = to_list_of_str(text)
else:
data = str(text).split('\n')
for line in data:
if len(line) > maxlen:
n = maxlen
for l in [line[i:i+n] for i in range(0, len(line), n)]:
paginator.add_line(l)
else:
paginator.add_line(line)
return paginator.pages
async def run_command(args):
# Create subprocess
process = await asyncio.create_subprocess_shell(
args,
# stdout must a pipe to be accessible as process.stdout
stdout=asyncio.subprocess.PIPE)
# Wait for the subprocess to finish
stdout, stderr = await process.communicate()
# Return stdout
return stdout.decode().strip()
+32 -32
View File
@@ -1,7 +1,7 @@
import discord
from discord.ext import commands
import json
from src.imports import checks
from .imports import checks
config_dir = 'config'
extension_dir = 'extensions'
@@ -17,11 +17,12 @@ class Patreon:
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def get_patreon_links(self, ctx, target: discord.Member=None):
"""Prints Patreon information for creators on the server."""
if await self.bot.db_con.fetchval('select patreon_enabled from guild_config where guild_id = $1', ctx.guild.id):
patreon_info = await self.bot.db_con.fetchrow('select patreon_message,patreon_links from guild_config '
'where guild_id = $1', ctx.guild.id)
message = patreon_info['patreon_message'].replace('\\n', '\n')
patreon_links = json.loads(patreon_info['patreon_links'])
if self.bot.con.one('select patreon_enabled from guild_config where guild_id = %(id)s', {'id': ctx.guild.id}):
patreon_info = self.bot.con.one('select patreon_message,patreon_links\
from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
message = patreon_info[0].replace('\\n', '\n')
patreon_links = json.loads(patreon_info[1])
for key in patreon_links:
message = message + '\n{0}: {1}'.format(key, patreon_links[key])
if target is None:
@@ -33,23 +34,23 @@ class Patreon:
@commands.command(aliases=['patreon_message'])
async def set_patreon_message(self, ctx, message):
if await checks.is_admin(self.bot, ctx):
patreon_message = await self.bot.db_con.fetchval('select patreon_message from guild_config '
'where guild_id = $1', ctx.guild.id)
if checks.is_admin(self.bot, ctx):
patreon_message = self.bot.con.one('select patreon_message from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if message == patreon_message:
await ctx.send('That is already the current message for this guild.')
else:
await self.bot.db_con.execute('update guild_config set patreon_message = $2 where guild_id = $1',
ctx.guild.id, message)
self.bot.con.run('update guild_config set patreon_message = %(message)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'message': message})
await ctx.send(f'The patreon message for this guild has been set to:\n{message}')
else:
await ctx.send(f'You are not authorized to run this command.')
@commands.command(aliases=['add_patreon', 'set_patreon'])
async def add_patreon_info(self, ctx, name, url):
if await checks.is_admin(self.bot, ctx):
patreon_info = await self.bot.db_con.fetchval('select patreon_links from guild_config where guild_id = $1',
ctx.guild.id)
if checks.is_admin(self.bot, ctx):
patreon_info = self.bot.con.one('select patreon_links from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
patreon_links = {}
update = 0
if patreon_info:
@@ -57,8 +58,8 @@ class Patreon:
if name in patreon_links:
update = 1
patreon_links[name] = url
await self.bot.db_con.execute('update guild_config set patreon_links = $2 where guild_id = $1',
ctx.guild.id, json.dumps(patreon_links))
self.bot.con.run('update guild_config set patreon_links = %(links)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'links': json.dumps(patreon_links)})
await ctx.send(f"The Patreon link for {name} has been "
f"{'updated to the new url.' if update else'added to the config for this guild.'}")
else:
@@ -66,15 +67,15 @@ class Patreon:
@commands.command(aliases=['remove_patreon'])
async def remove_patreon_info(self, ctx, name):
if await checks.is_admin(self.bot, ctx):
patreon_info = await self.bot.db_con.fetchval('select patreon_links from guild_config where guild_id = $1',
ctx.guild.id)
if checks.is_admin(self.bot, ctx):
patreon_info = self.bot.con.one('select patreon_links from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if patreon_info:
patreon_links = json.loads(patreon_info)
if name in patreon_links:
del patreon_links[name]
await self.bot.db_con.execute('update guild_config set patreon_links = $2 where guild_id = $1',
ctx.guild.id, json.dumps(patreon_links))
self.bot.con.run('update guild_config set patreon_links = %(links)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'links': json.dumps(patreon_links)})
await ctx.send(f'The Patreon link for {name} has been removed from the config for this guild.')
return
else:
@@ -86,18 +87,18 @@ class Patreon:
@commands.command()
async def enable_patreon(self, ctx, state: bool=True):
if await checks.is_admin(self.bot, ctx):
patreon_status = await self.bot.db_con.fetchval('select patreon_enabled from guild_config '
'where guild_id = $1', ctx.guild.id)
if checks.is_admin(self.bot, ctx):
patreon_status = self.bot.con.one('select patreon_enabled from guild_config where guild_id = %(id)s',
{'id': ctx.guild.id})
if patreon_status and state:
await ctx.send('Patreon is already enabled for this guild.')
elif patreon_status and not state:
await self.bot.db_con.execute('update guild_config set patreon_enabled = $2 where guild_id = $1',
ctx.guild.id, state)
self.bot.con.run('update guild_config set patreon_enabled = %(state)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'state': state})
await ctx.send('Patreon has been disabled for this guild.')
elif not patreon_status and state:
await self.bot.db_con.execute('update guild_config set patreon_enabled = $2 where guild_id = $1',
ctx.guild.id, state)
self.bot.con.run('update guild_config set patreon_enabled = %(state)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'state': state})
await ctx.send('Patreon has been enabled for this guild.')
elif not patreon_status and not state:
await ctx.send('Patreon is already disabled for this guild.')
@@ -106,10 +107,9 @@ class Patreon:
@commands.cooldown(1, 5, type=commands.BucketType.user)
async def referral_links(self, ctx, target: discord.Member=None):
"""Prints G-Portal Referral Links."""
if await self.bot.db_con.fetchval('select referral_enabled from guild_config where guild_id = $1',
ctx.guild.id):
referral_info = await self.bot.db_con.fetchval('select referral_message,referral_links from guild_config '
'where guild_id = $1', ctx.guild.id)
if self.bot.con.one('select referral_enabled from guild_config where guild_id = %(id)s', {'id': ctx.guild.id}):
referral_info = self.bot.con.one('select referral_message,referral_links from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id})
message = referral_info[0]
referral_links = json.loads(referral_info[1])
for key in referral_links:
+48 -56
View File
@@ -7,7 +7,7 @@ import logging
from datetime import datetime
import asyncio
import traceback
from src.imports import checks
from .imports import checks
config_dir = 'config'
admin_id_file = 'admin_ids'
@@ -120,7 +120,6 @@ class Rcon:
True)
con.exec_command('ServerChatToPlayer "{0}" GeeksBot: Admin Geeks have been notified you need assistance. '
'Please be patient.'.format(player))
# noinspection PyProtectedMember
con._sock.close()
for role in admin_roles:
msg = '{0} {1}'.format(msg, discord.utils.get(ctx.guild.roles, id=admin_roles[role]).mention)
@@ -138,16 +137,15 @@ class Rcon:
first_last
"first last"
To view all the valid ARK servers for this guild see list_ark_servers."""
if await checks.is_rcon_admin(self.bot, ctx):
if checks.is_rcon_admin(self.bot, ctx):
if server is not None:
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
'from guild_config where guild_id = $1',
ctx.guild.id))
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
server = server.replace('_', ' ').title()
if server in rcon_connections:
rcon_connections[server]["monitoring_chat"] = 1
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
self.bot.con.run('update guild_config set rcon_connections = %(json)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'json': json.dumps(rcon_connections)})
channel = self.bot.get_channel(rcon_connections[server]['game_chat_chan_id'])
await channel.send('Started monitoring on the {0} server.'.format(server))
await ctx.message.add_reaction('')
@@ -160,7 +158,6 @@ class Rcon:
True)
messages = await self.bot.loop.run_in_executor(None, self.server_chat_background_process,
ctx.guild.id, con)
# noinspection PyProtectedMember
con._sock.close()
except TimeoutError:
rcon_log.error(traceback.format_exc())
@@ -183,10 +180,8 @@ class Rcon:
message = func(ctx, message, rcon_connections['server'])
await channel.send('{0}'.format(message))
await asyncio.sleep(1)
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
'from guild_config '
'where guild_id = $1',
ctx.guild.id))
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
await channel.send('Monitoring Stopped')
else:
await ctx.send(f'Server not found: {server}')
@@ -200,16 +195,15 @@ class Rcon:
async def end_monitor_chat(self, ctx, *, server=None):
"""Ends chat monitoring on the specified server.
Context is the same as monitor_chat"""
if await checks.is_rcon_admin(self.bot, ctx):
if checks.is_rcon_admin(self.bot, ctx):
if server is not None:
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
'from guild_config where guild_id = $1',
ctx.guild.id))
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
server = server.replace('_', ' ').title()
if server in rcon_connections:
rcon_connections[server]["monitoring_chat"] = 0
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
self.bot.con.run('update guild_config set rcon_connections = %(json)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'json': json.dumps(rcon_connections)})
else:
await ctx.send(f'Server not found: {server}')
else:
@@ -229,9 +223,9 @@ class Rcon:
first_last
"first last"
To view all the valid ARK servers for this guild see list_ark_servers."""
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if server is not None:
server = server.replace('_', ' ').title()
if server in rcon_connections:
@@ -247,10 +241,9 @@ class Rcon:
'"ip" port "password" if you would like to get info from it.'.format(server))
else:
for server in rcon_connections:
msg = await ctx.send('Getting Data for the {0} server'.format(server.title()))
# noinspection PyBroadException
try:
connection_info = rcon_connections[server]
msg = await ctx.send('Getting Data for the {0} server'.format(server.title()))
async with ctx.channel.typing():
message = self._listplayers(connection_info)
except Exception as e:
@@ -267,10 +260,10 @@ class Rcon:
async def add_rcon_server(self, ctx, server, ip, port, password):
"""Adds the specified server to the current guild\'s rcon config.
All strings (<server>, <ip>, <password>) must be contained inside double quotes."""
if await checks.is_rcon_admin(self.bot, ctx):
if checks.is_rcon_admin(self.bot, ctx):
server = server.title()
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if server not in rcon_connections:
rcon_connections[server] = {
'ip': ip,
@@ -281,8 +274,8 @@ class Rcon:
'msg_chan_id': 0,
'monitoring_chat': 0
}
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
self.bot.con.run('update guild_config set rcon_connections = %(connections)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'connections': json.dumps(rcon_connections)})
await ctx.send('{0} server has been added to my configuration.'.format(server))
else:
await ctx.send('This server name is already in my configuration. Please choose another.')
@@ -296,14 +289,14 @@ class Rcon:
async def remove_rcon_server(self, ctx, server):
"""removes the specified server from the current guild\'s rcon config.
All strings <server> must be contained inside double quotes."""
if await checks.is_rcon_admin(self.bot, ctx):
if checks.is_rcon_admin(self.bot, ctx):
server = server.title()
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if server in rcon_connections:
del rcon_connections[server]
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
self.bot.con.run('update guild_config set rcon_connections = %(connections)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'connections': json.dumps(rcon_connections)})
await ctx.send('{0} has been removed from my configuration.'.format(server))
else:
await ctx.send('{0} is not in my configuration.'.format(server))
@@ -316,11 +309,10 @@ class Rcon:
"""Adds the included Steam 64 IDs to the running whitelist on all the ARK servers in the current guild\'s rcon config.
Steam 64 IDs should be a comma seperated list of IDs.
Example: 76561198024193239,76561198024193239,76561198024193239"""
if await checks.is_rcon_admin(self.bot, ctx):
if checks.is_rcon_admin(self.bot, ctx):
if steam_ids is not None:
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections '
'from guild_config where guild_id = $1',
ctx.guild.id))
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
error = 0
error_msg = ''
success_msg = 'Adding to the running whitelist on all servers.'
@@ -364,9 +356,9 @@ class Rcon:
"""Runs SaveWorld on the specified ARK server.
If a server is not specified it will default to running saveworld on all servers in the guild\'s config.
Will print out "World Saved" for each server when the command completes successfully."""
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
success_msg = 'Running saveworld'
if server is None:
success_msg += ' on all the servers:'
@@ -377,7 +369,7 @@ class Rcon:
await msg.edit(content=success_msg.strip())
message = await self.bot.loop.run_in_executor(None, self._saveworld, rcon_connections[server])
except Exception as e:
success_msg = '{0}\n{1}'.format(success_msg, e)
success_msg = '{0}\n{1}'.format(success_msg, e.strip())
await msg.edit(content=success_msg.strip())
else:
success_msg = '{0}\n{1}'.format(success_msg, message.strip())
@@ -406,9 +398,9 @@ class Rcon:
"""Sends a broadcast message to all servers in the guild config.
The message will be prefixed with the Discord name of the person running the command.
Will print "Success" for each server once the broadcast is sent."""
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if message is not None:
message = f'{ctx.author.display_name}: {message}'
success_msg = f'Broadcasting "{message}" to all servers.'
@@ -422,7 +414,7 @@ class Rcon:
rcon_connections[server],
message)
except Exception as e:
success_msg = '{0}\n{1}'.format(success_msg, e)
success_msg = '{0}\n{1}'.format(success_msg, e.strip())
await msg.edit(content=success_msg.strip())
else:
for mesg in messages:
@@ -443,9 +435,9 @@ class Rcon:
The message will be prefixed with the Discord name of the person running the command.
If <server> has more than one word in it's name it will either need to be surrounded
by double quotes or the words separated by _"""
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
if server is not None:
server = server.replace('_', ' ').title()
if message is not None:
@@ -481,9 +473,9 @@ class Rcon:
and these channel's permissions will be synced to the category.
These channels will be added to the guild's rcon config and are where the
server chat messages will be sent when monitor_chat is run."""
if await checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
if checks.is_rcon_admin(self.bot, ctx):
rcon_connections = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
edited = 0
category = discord.utils.get(ctx.guild.categories, name='Server Chats')
if category is None:
@@ -506,8 +498,8 @@ class Rcon:
rcon_connections[server]['game_chat_chan_id'] = chan.id
edited = 1
if edited == 1:
await self.bot.db_con.execute('update guild_config set rcon_connections = $2 where guild_id = $1',
ctx.guild.id, json.dumps(rcon_connections))
self.bot.con.run('update guild_config set rcon_connections = %(json)s where guild_id = %(id)s',
{'id': ctx.guild.id, 'json': json.dumps(rcon_connections)})
await ctx.message.add_reaction('')
else:
await ctx.send(f'You are not authorized to run this command.')
@@ -517,8 +509,8 @@ class Rcon:
@commands.check(checks.is_restricted_chan)
async def list_ark_servers(self, ctx):
"""Returns a list of all the ARK servers in the current guild\'s config."""
servers = json.loads(await self.bot.db_con.fetchval('select rcon_connections from guild_config '
'where guild_id = $1', ctx.guild.id))
servers = json.loads(self.bot.con.one('select rcon_connections from guild_config\
where guild_id = %(id)s', {'id': ctx.guild.id}))
em = discord.Embed(style='rich',
title=f'__**There are currently {len(servers)} ARK servers in my config:**__',
color=discord.Colour.green()
+27 -22
View File
@@ -7,7 +7,7 @@ import inspect
import textwrap
from contextlib import redirect_stdout
import io
from src.imports.utils import run_command, format_output, Paginator
from .imports.utils import paginate, run_command
ownerids = [351794468870946827, 275280442884751360]
ownerid = 351794468870946827
@@ -37,7 +37,6 @@ class Repl:
async def _eval(self, ctx, *, body: str):
if ctx.author.id != ownerid:
return
pag = Paginator(self.bot)
env = {
'bot': self.bot,
'ctx': ctx,
@@ -61,10 +60,8 @@ class Repl:
with redirect_stdout(stdout):
ret = await func()
except Exception:
pag.add(stdout.getvalue())
pag.add(traceback.format_exc())
for page in pag.pages():
await ctx.send(page)
value = stdout.getvalue()
await ctx.send('```py\n{}{}\n```'.format(value, traceback.format_exc()))
else:
value = stdout.getvalue()
# noinspection PyBroadException
@@ -72,12 +69,17 @@ class Repl:
await ctx.message.add_reaction('')
except Exception:
pass
value = format_output(value)
pag.add(value)
pag.add(f'\nReturned: {ret}')
self._last_result = ret
for page in pag.pages():
await ctx.send(page)
if ret is None:
if value:
for page in paginate(value):
await ctx.send(page)
else:
self._last_result = ret
if value:
for page in paginate(value):
await ctx.send(page)
for page in paginate(ret):
await ctx.send(page)
@commands.command(hidden=True)
async def repl(self, ctx):
@@ -141,11 +143,12 @@ class Repl:
fmt = '{}'.format(value)
try:
if fmt is not None:
pag = Paginator(self.bot)
pag.add(fmt)
for page in pag.pages():
await response.channel.send(page)
await ctx.send(response.channel)
if len(fmt) > 1990:
for page in paginate(fmt):
await response.channel.send(page)
await ctx.send(response.channel)
else:
await response.channel.send(f'```py\n{fmt}\n```')
except discord.Forbidden:
pass
except discord.HTTPException as e:
@@ -156,14 +159,16 @@ class Repl:
if ctx.author.id != ownerid:
return
try:
body = self.cleanup_code(body)
pag = Paginator(self.bot)
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 10))
for page in pag.pages():
body = self.cleanup_code(body).split(' ')
result = await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 10)
value = result
for page in paginate(value):
await ctx.send(page)
await ctx.message.add_reaction('')
except asyncio.TimeoutError:
await ctx.send(f"Command did not complete in the time allowed.")
value = f"Command did not complete in the time allowed."
for page in paginate(value):
await ctx.send(page)
await ctx.message.add_reaction('')
+63 -311
View File
@@ -7,19 +7,10 @@ import psutil
from datetime import datetime, timedelta
import asyncio
import async_timeout
from src.imports import checks, utils
from .imports import checks
import pytz
import gspread
from oauth2client.service_account import ServiceAccountCredentials
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from io import BytesIO
from itertools import chain
import numpy as np
from dateutil.parser import parse
from copy import copy
config_dir = 'config/'
admin_id_file = 'admin_ids'
@@ -31,7 +22,6 @@ invite_match = '(https?://)?(www.)?discord(app.com/(invite|oauth2)|.gg|.io)/[\w\
utils_log = logging.getLogger('utils')
clock_emojis = ['🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚']
replace_tzs = {'MST': 'US/Mountain', 'HST': 'US/Hawaii', 'EST': 'US/Eastern'}
class Utils:
@@ -54,20 +44,20 @@ class Utils:
msg = await self.bot.wait_for('message', timeout=5, check=check)
self.bot.ping_times[i]['rec'] = msg
@commands.command(hidden=True)
@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(hidden=True)
@commands.command()
@commands.is_owner()
async def sysinfo(self, ctx):
"""Gets system status for my server."""
await ctx.send(f'```ml\n'
f'CPU Percentages: {psutil.cpu_percent(percpu=True)}\n'
f'Memory Usage: {psutil.virtual_memory().percent}%\n'
f'Disc Usage: {psutil.disk_usage("/").percent}%\n'
f'```')
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: str):
@@ -178,10 +168,7 @@ class Utils:
@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
For more detailed information set the <mode> as comp and it will test the ping
<count> number of times."""
"""Check the Bot\'s connection to Discord"""
em = discord.Embed(style='rich',
title=f'Pong 🏓',
color=discord.Colour.green()
@@ -189,8 +176,8 @@ class Utils:
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**\n' \
f'Discord Latency: **{math.ceil(self.bot.latency*1000)}ms**'
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':
@@ -219,8 +206,8 @@ class Utils:
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')}\n" \
f"Response Received: {datetime.strftime(now, '%H:%M:%S.%f')}\n" \
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)
@@ -233,11 +220,11 @@ class Utils:
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)
await msg.edit(embed=em)
@commands.group(case_insensitive=True)
async def admin(self, ctx):
"""Group for Admin help requests"""
"""Run help admin for more info"""
pass
@admin.command(name='new', aliases=['nr'])
@@ -249,20 +236,25 @@ class Utils:
if ctx.guild:
if request_msg is not None:
if len(request_msg) < 1000:
await self.bot.db_con.execute('insert into admin_requests (issuing_member_id, guild_orig, '
'request_text, request_time) values ($1, $2, $3, $4)',
ctx.author.id, ctx.guild.id, request_msg, ctx.message.created_at)
channel = await self.bot.db_con.fetchval(f'select admin_chat from guild_config where guild_id = $1',
ctx.guild.id)
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 = ''
roles = await self.bot.db_con.fetchval(f'select admin_roles,rcon_admin_roles from guild_config '
f'where guild_id = $1', ctx.guild.id)
request_id = await self.bot.db_con.fetchval(f'select id from admin_requests where '
f'issuing_member_id = $1 and request_time = $2',
ctx.author.id, ctx.message.created_at)
admin_roles = json.loads(roles).values()
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} " \
@@ -281,7 +273,7 @@ class Utils:
@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):
"""List of all active Admin help requests
"""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.
@@ -294,10 +286,10 @@ class Utils:
title=f'Admin Help Requests',
color=discord.Colour.green()
)
if await checks.is_admin(self.bot, ctx) or await checks.is_rcon_admin(self.bot, ctx):
if checks.is_admin(self.bot, ctx) or checks.is_rcon_admin(self.bot, ctx):
if assigned_to is None:
requests = await self.bot.db_con.fetch(f'select * from admin_requests where guild_orig = $1 '
f'and completed_time is null', ctx.guild.id)
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:
@@ -314,11 +306,11 @@ class Utils:
else:
em.add_field(name='There are no pending requests for this guild.', value='', inline=False)
else:
if await checks.check_admin_role(self.bot, ctx, assigned_to)\
or await checks.check_rcon_role(self.bot, ctx, assigned_to):
requests = await self.bot.db_con.fetch('select * from admin_requests where assigned_to = $1 '
'and guild_orig = $2 and completed_time is null',
assigned_to.id, ctx.guild.id)
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:
@@ -330,16 +322,15 @@ class Utils:
"",
inline=False)
else:
em.add_field(name=f'There are no pending requests for '
f'{assigned_to.display_name} on this guild.',
em.add_field(name=f'There are no pending requests for {assigned_to.display_name} on this guild.',
value='',
inline=False)
else:
em.title = f'{assigned_to.display_name} is not an admin in this guild.'
else:
requests = await self.bot.db_con.fetch('select * from admin_requests where issuing_member_id = $1 '
'and guild_orig = $2 and completed_time is null',
ctx.author.id, ctx.guild.id)
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:
@@ -361,7 +352,7 @@ class Utils:
"""Allows Admin to close admin help tickets.
[request_id] must be a valid integer pointing to an open Request ID
"""
if await checks.is_admin(self.bot, ctx) or await checks.is_rcon_admin(self.bot, ctx):
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:
@@ -370,13 +361,14 @@ class Utils:
except ValueError:
await ctx.send(f'{request_id} is not a valid request id.')
else:
request = await self.bot.db_con.fetchrow(f'select * from admin_requests where id = $1',
request_id)
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:
await self.bot.db_con.execute('update admin_requests set completed_time = $1 where '
'id = $2', ctx.message.created_at, request_id)
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.')
@@ -394,8 +386,7 @@ class Utils:
@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 given
"""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:
@@ -432,12 +423,7 @@ class Utils:
@commands.command(name='localtime', aliases=['time', 'lt'])
@commands.cooldown(1, 3, type=commands.BucketType.user)
async def get_localtime(self, ctx, timezone: str='Anchorage'):
"""Shows the current time in the timezone given
This defaults to the Bot's local timezone of Anchorage Alaska USA if none are given."""
em = discord.Embed()
try:
tz = pytz.timezone(timezone)
localtime = datetime.now(tz=tz)
@@ -458,91 +444,13 @@ class Utils:
em.colour = discord.Colour.red()
await ctx.send(embed=em)
# noinspection PyUnboundLocalVariable
@commands.command(name='gettimein', aliases=['timein', 'gti'])
@commands.cooldown(1, 3, type=commands.BucketType.user)
async def get_time_in_timezone(self, ctx, timezone: str='US/Eastern', *, time: str=None):
"""Convert the time provided to given timezone
Attempts to process the given time and timezone and convert into the given timezone.
Example: g$gti CET Friday June 15 2018 US/Alaska
This will be processed into a datetime with US/Alaska set as the timezone and will
convert it into CET timezone and return both times."""
em = discord.Embed()
if time is None:
em.set_footer(text='Time not given... using current UTC time.')
in_time = datetime.utcnow()
parsed_tz = pytz.timezone('UTC')
else:
try:
orig_time = copy(time)
split_time = time.split()
try:
parsed_tz = pytz.timezone(replace_tzs.get(split_time[-1].upper()) or split_time[-1])
time = utils.replace_text_ignorecase(time, old=split_time[-1], new='')
except pytz.exceptions.UnknownTimeZoneError:
for tz in pytz.all_timezones:
if split_time[-1].lower() in tz.lower():
time = utils.replace_text_ignorecase(time, old=split_time[-1], new='')
if tz in replace_tzs:
tz = replace_tzs['tz']
parsed_tz = pytz.timezone(tz)
break
else:
em.set_footer(text='Valid timezone not found in time string. Using UTC...')
parsed_tz = pytz.timezone('UTC')
if not time.isspace() and not time == '':
in_time = parse(time.upper())
in_time = parsed_tz.localize(in_time)
else:
em.set_footer(text='Time not given. Using current time.')
in_time = datetime.now(tz=parsed_tz)
except ValueError:
raise commands.CommandError(f'For some reason I can\'t parse this time string: \n'
f'{orig_time} {time} {parsed_tz}\n'
f'Examples of valid time strings are in my help documentation.\n'
f'Please try again.')
try:
out_tz = pytz.timezone(timezone)
except pytz.exceptions.UnknownTimeZoneError:
for tz in pytz.all_timezones:
if timezone.lower() in tz.lower():
out_tz = pytz.timezone(tz)
break
else:
out_tz = None
em.title = 'Unknown Timezone.'
em.colour = discord.Colour.red()
finally:
if out_tz:
out_time = in_time.astimezone(out_tz)
em.add_field(name=f'{parsed_tz}',
value=f'{clock_emojis[(in_time.hour % 12)]} {in_time.strftime("%c")}', inline=False)
em.add_field(name=f'{out_tz}',
value=f'{clock_emojis[(out_time.hour % 12)]} {out_time.strftime("%c")}', inline=False)
em.colour = self.bot.embed_color
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):
"""Purge messages from the current channel
By default this will only purge messages sent by Geeksbot and any messages that appear to
have called Geeksbot (aka start with one of the Geeksbot's prefixes for this Guild)
If you want to purge messages from a different user you must provide a number and member
Note: Geeksbot will not find <number> of messages by the given member, it will instead
search the last <number> messages in the channel and delete any by the given member"""
prefixes = await self.bot.db_con.fetchval('select prefix from guild_config '
'where guild_id = $1', ctx.guild.id)
def is_me(message):
nonlocal prefixes
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):
@@ -556,7 +464,7 @@ class Utils:
def is_author(message):
return message.author == ctx.author
if await checks.is_admin(self.bot, ctx):
if checks.is_admin(self.bot, ctx):
if member:
deleted = await ctx.channel.purge(limit=number, check=is_member)
if member != ctx.author:
@@ -572,12 +480,7 @@ class Utils:
@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'):
"""Purge all messages from the current channel
Will delete all of the last <number> of messages from the channel
If <contents> is not 'all' then only messages containing <contents>
will be deleted."""
if await checks.is_admin(self.bot, ctx):
if checks.is_admin(self.bot, ctx):
if contents != 'all':
deleted = await ctx.channel.purge(limit=number, check=lambda message: message.content == contents)
else:
@@ -590,24 +493,19 @@ class Utils:
@commands.command(name='google', aliases=['g', 'search'])
async def google_search(self, ctx, *, search):
"""WIP Search Google for the given string"""
res = self.bot.gcs_service.cse().list(q=search, cx=self.bot.bot_secrets['cx']).execute()
results = res['items']
pag = utils.Paginator(self.bot, max_line_length=100, embed=True)
pag.set_embed_meta(title='Google Search', description=f'Top results for "{search}"', color=self.bot.embed_color)
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:
pag.add(f'\uFFF6{result["title"]}\n{result["link"]}', keep_intact=True)
pag.add(f'{result["snippet"]}')
pag.add('\uFFF7\n\uFFF8')
msg = await ctx.send('Starting Book')
book = utils.Book(pag, (msg, ctx.channel, self.bot, ctx.message))
await book.create_book()
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):
"""Access Google Sheets and looks for the member"""
if await checks.is_admin(self.bot, ctx):
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)
@@ -630,152 +528,6 @@ class Utils:
value=f'Steam ID: {steam[i]}\nPatreon Level: {tier[i]}\nPatron of: {patron[i]}')
await ctx.send(embed=em)
@commands.command(name='iss')
async def iss_loc(self, ctx):
"""Locate the International Space Station
Gets the location of the ISS and display on a
Blue Marble map."""
def gen_image(iss_loc):
lat = iss_loc['latitude']
lon = iss_loc['longitude']
plt.figure(figsize=(5, 5))
m = Basemap(projection='ortho', resolution=None, lat_0=lat, lon_0=lon)
m.bluemarble(scale=0.5)
x, y = m(lon, lat)
plt.plot(x, y, 'ok', markersize=10, color='red')
plt.text(x, y, ' ISS', fontsize=20, color='red')
plt.tight_layout()
img = BytesIO()
plt.savefig(img, format='png', transparent=True)
img.seek(0)
self.bot.loop.create_task(ctx.send('Current ISS Location', file=discord.File(img, 'output.png')))
async with ctx.typing():
async with self.bot.aio_session.get('https://api.wheretheiss.at/v1/satellites/25544') as response:
loc = await response.json()
await self.bot.loop.run_in_executor(self.bot.tpe, gen_image, loc)
@commands.command(name='location', aliases=['loc', 'map'])
async def map_location(self, ctx, *, location):
"""WIP Displays the given location on a map
Searches for the location provided and plots the Lat Long on a map.
Note: This is SLOW!!! Be prepared to wait up to a minute for the result"""
def draw_map(m, scale=1):
# draw a shaded-relief image
m.shadedrelief(scale=scale)
m.fillcontinents(color="#FFDDCC", lake_color='#DDEEFF')
m.drawmapboundary(fill_color="#DDEEFF")
m.drawcoastlines(color='gray')
m.drawcountries(color='gray')
m.drawstates(color='gray')
# lats and longs are returned as a dictionary
lats = m.drawparallels(np.linspace(-90, 90, 30))
lons = m.drawmeridians(np.linspace(-180, 180, 90))
# keys contain the plt.Line2D instances
lat_lines = chain(*(tup[1][0] for tup in lats.items()))
lon_lines = chain(*(tup[1][0] for tup in lons.items()))
all_lines = chain(lat_lines, lon_lines)
# cycle through these lines and set the desired style
for line in all_lines:
line.set(linestyle='-', alpha=0.3, color='gray')
def gen_image(loc):
lat = loc['lat']
lon = loc['lng']
plt.figure(figsize=(4, 4))
m = Basemap(projection='lcc', width=2E6, height=2E6, resolution='i', lat_0=lat, lon_0=lon)
draw_map(m)
x, y = m(lon, lat)
plt.plot(x, y, 'ok', markersize=5, color='red')
plt.text(x, y, f' {location.title()}', fontsize=12, color='red')
plt.tight_layout()
img = BytesIO()
plt.savefig(img, format='png', transparent=True)
img.seek(0)
self.bot.loop.create_task(ctx.send(file=discord.File(img, f'{location} map.png')))
self.bot.loop.create_task(ctx.trigger_typing())
msg = await ctx.send(f'Checking on location data for {location.title()}')
async with ctx.typing():
async with self.bot.aio_session.get(
f'https://api.opencagedata.com/geocode/v1/json?q={location}&key={self.bot.geo_api}') as result:
data = await result.json()
if data['total_results'] != 0:
location_data = data['results'][0]['geometry']
await msg.edit(content=f'Got Location. Please wait, Generating the image can take up to a minute.')
async with ctx.typing():
await self.bot.loop.run_in_executor(self.bot.tpe, gen_image, location_data)
await msg.delete()
else:
await msg.edit(content=f'I can\'t find any data for that location.\nPlease try again.')
@commands.command(name='help', aliases=['h'])
@commands.cooldown(1, 5, commands.BucketType.user)
async def custom_help(self, ctx, *, command: str=None):
"""This help message"""
pag = utils.Paginator(self.bot, embed=True, max_line_length=48)
prefixes = await self.bot.get_custom_prefix(self.bot, ctx.message)
if isinstance(prefixes, list):
prefixes = ', '.join(prefixes)
owner = await self.bot.get_user_info(self.bot.owner_id)
if command is None:
pag.set_embed_meta(title='Geeksbot Help',
description=f'For more information about a command please run\n'
f'{prefixes.split(",")[0]}help [group] <command>',
thumbnail=f'{ctx.guild.me.avatar_url}')
pag.add(f"\uFFF6Welcome to Geeksbot's help command.\n"
f"< {self.bot.description} >\n\n"
f"Below you will find some basic information about me.\n\n"
f"Version: <{self.bot.__version__}>\n\n"
f"Owner: <Dusty.P>\n"
f"> Username: {owner.name}#{owner.discriminator}\n"
f"> ID: {owner.id}\n\n"
f"Prefixes available for this guild:\n"
f"> {prefixes}\n\uFFF7\n\uFFF8")
for cog in sorted(self.bot.cogs):
for command in sorted(self.bot.get_cog_commands(cog), key=lambda x: x.name):
if not command.hidden:
pag.add(f'\uFFF6{command.name}')
pag.add(f'> {command.short_doc}', truncate=True)
try:
for com in sorted(command.commands, key=lambda x: x.name):
if not com.hidden:
pag.add(f'# {com.name}')
pag.add(f'> {com.short_doc}', truncate=True)
except AttributeError as e:
pass
pag.add('\uFFF7')
pag.add('\uFFF8')
else:
pag.set_embed_meta(title='Geeksbot Help',
thumbnail=f'{ctx.guild.me.avatar_url}')
command = command.split(maxsplit=1)
if command[0] in self.bot.all_commands:
if len(command) > 1 and self.bot.all_commands[command[0]].group:
command = self.bot.all_commands[command[0]].all_commands.get(command[1], None)
else:
command = self.bot.all_commands[command[0]]
else:
command = None
if command and not command.hidden:
pag.add(f'\uFFF6{command.name}')
pag.add(f'Usage: {prefixes.split()[0]}{command.signature}\n')
pag.add(f'\uFFF0\n{command.help}')
else:
pag.add('\uFFF6There is no command by that name.\n>')
book = utils.Book(pag, (None, ctx.channel, self.bot, ctx.message))
await book.create_book()
def setup(bot):
bot.add_cog(Utils(bot))
+28 -51
View File
@@ -1,15 +1,13 @@
from typing import Dict
import discord
from discord.ext import commands
import logging
from datetime import datetime
import json
import aiohttp
from postgres import Postgres
from collections import deque
from googleapiclient.discovery import build
from concurrent import futures
from src.shared_libs import database
log_format = '{asctime}.{msecs:03.0f}|{levelname:<8}|{name}::{message}'
date_format = '%Y.%m.%d %H.%M.%S'
@@ -25,7 +23,7 @@ formatter = logging.Formatter(log_format, style='{', datefmt=date_format)
console_handler.setFormatter(formatter)
logging.getLogger('').addHandler(console_handler)
config_dir = 'src/config/'
config_dir = 'config/'
admin_id_file = 'admin_ids'
extension_dir = 'exts'
owner_id = 351794468870946827
@@ -37,70 +35,48 @@ emojis: Dict[str, str] = {
'x': '',
'y': '',
'poop': '💩',
'boom': '💥',
}
description = 'I am Geeksbot v0.1! Fear me!'
class Geeksbot(commands.Bot):
def __init__(self, **kwargs):
kwargs["command_prefix"] = self.get_custom_prefix
self.description = 'I am Geeksbot! Fear me!'
kwargs['description'] = self.description
super().__init__(**kwargs)
self.aio_session = aiohttp.ClientSession(loop=self.loop)
with open(f'{config_dir}{bot_config_file}') as file:
self.bot_config = json.load(file)
with open(f'{config_dir}{secrets_file}') as file:
self.bot_secrets = json.load(file)
# with open(f'{config_dir}{profane_words_file}') as file:
# self.profane_words = file.readlines()
self.guild_config = {}
self.infected = {}
self.TOKEN = self.bot_secrets['token']
self.embed_color = discord.Colour.from_rgb(49, 107, 111)
self.error_color = discord.Colour.from_rgb(142, 29, 31)
del self.bot_secrets['token']
self.db_con = database.DatabaseConnection(**self.bot_secrets['db_con'])
self.con = Postgres(f" host={self.bot_secrets['db_con']['host']}\
port={self.bot_secrets['db_con']['port']}\
dbname={self.bot_secrets['db_con']['db_name']}\
connect_timeout=10 user={self.bot_secrets['db_con']['user']}\
password={self.bot_secrets['db_con']['password']}")
del self.bot_secrets['db_con']
self.default_prefix = 'g$'
self.voice_chans = {}
self.spam_list = {}
self.owner_id = 351794468870946827
self.__version__ = 'v1.0.0'
self.gcs_service = build('customsearch', 'v1', developerKey=self.bot_secrets['google_search_key'])
self.tpe = futures.ThreadPoolExecutor()
self.geo_api = '2d4e419c2be04c8abe91cb5dd1548c72'
self.unicode_emojis: Dict[str, str] = {
'x': '',
'y': '',
'poop': '💩',
'boom': '💥',
'left_fist': '🤛',
'lock': '🔒',
}
self.book_emojis: Dict[str, str] = {
'unlock': '🔓',
'start': '',
'back': '',
'hash': '#\N{COMBINING ENCLOSING KEYCAP}',
'forward': '',
'end': '',
'close': '🇽',
}
async def logout(self):
await self.db_con.close()
super().logout()
@staticmethod
async def get_custom_prefix(bot_inst, message):
return await bot_inst.db_con.fetchval('select prefix from guild_config where guild_id = $1',
message.guild.id) or bot_inst.default_prefix
async def get_custom_prefix(self, bot_inst, message):
return self.con.one('select prefix from guild_config where guild_id = %(id)s', {'id': message.guild.id})\
or self.default_prefix
async def load_ext(self, ctx, mod=None):
self.load_extension('src.{0}.{1}'.format(extension_dir, mod))
self.load_extension('{0}.{1}'.format(extension_dir, mod))
if ctx is not None:
await ctx.send('{0} loaded.'.format(mod))
async def unload_ext(self, ctx, mod=None):
self.unload_extension('src.{0}.{1}'.format(extension_dir, mod))
self.unload_extension('{0}.{1}'.format(extension_dir, mod))
if ctx is not None:
await ctx.send('{0} unloaded.'.format(mod))
@@ -109,7 +85,7 @@ class Geeksbot(commands.Bot):
self.aio_session.close() # aiohttp is drunk and can't decide if it's a coro or not
bot = Geeksbot(case_insensitive=True)
bot = Geeksbot(description=description, case_insensitive=True)
@bot.command(hidden=True)
@@ -144,14 +120,14 @@ async def unload(ctx, mod):
async def on_message(ctx):
if not ctx.author.bot:
if ctx.guild:
if int(await bot.db_con.fetchval("select channel_lockdown from guild_config where guild_id = $1",
ctx.guild.id)):
if ctx.channel.id in json.loads(await bot.db_con.fetchval("select allowed_channels from guild_config "
"where guild_id = $1",
ctx.guild.id)):
if int(bot.con.one(f"select channel_lockdown from guild_config where guild_id = %(id)s",
{'id': ctx.guild.id})):
if ctx.channel.id in json.loads(bot.con.one(f"select allowed_channels from guild_config "
f"where guild_id = %(id)s",
{'id': ctx.guild.id})):
await bot.process_commands(ctx)
elif ctx.channel.id == 418452585683484680:
prefix = await bot.db_con.fetchval('select prefix from guild_config where guild_id = $1', ctx.guild.id)
prefix = bot.con.one('select prefix from guild_config where guild_id = %(id)s', {'id': ctx.guild.id})
prefix = prefix[0] if prefix else bot.default_prefix
ctx.content = f'{prefix}{ctx.content}'
await bot.process_commands(ctx)
@@ -163,19 +139,20 @@ async def on_message(ctx):
@bot.event
async def on_ready():
bot.remove_command('help')
bot.recent_msgs = {}
for guild in bot.guilds:
bot.recent_msgs[guild.id] = deque(maxlen=50)
logging.info('Logged in as {0.name}|{0.id}'.format(bot.user))
load_list = bot.bot_config['load_list']
for load_item in load_list:
await bot.load_ext(None, f'{load_item}')
logging.info('Extension Loaded: {0}'.format(load_item))
logging.info('Done loading, Geeksbot is active.')
with open(f'{config_dir}reboot', 'r') as f:
reboot = f.readlines()
if int(reboot[0]) == 1:
await bot.get_channel(int(reboot[1])).send('Restart Finished.')
with open(f'{config_dir}reboot', 'w') as f:
f.write(f'0')
logging.info('Done loading, Geeksbot is active.')
bot.run(bot.TOKEN)
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
until python -m src; do
until python /home/dusty/bin/geeksbot/geeksbot.py; do
echo "Geeksbot shutdown with error: $?. Restarting..." >&2
sleep 1
done
+35
View File
@@ -0,0 +1,35 @@
@checks.no_bots()
@commands.cooldown(1,5,commands.BucketType.user)
@commands.command()
async def captcha(self, ctx, type, *, text):
type = type.lower()
if type not in "checked unchecked loading".split():
raise commands.BadArgument(f"Invalid type {type!r}. Available "
"types: `unchecked`, `loading`, `checked`")
font = ImageFont.truetype("Roboto-Regular.ttf", 14)
async with ctx.typing():
img = Image.open(f"blank-captcha-{type}.png")
img.load()
d = ImageDraw.Draw(img)
fnc = functools.partial(d.text, (53,30), text, fill=(0,0,0,255),
font=font)
await self.bot.loop.run_in_executor(None, fnc)
img.save("captcha.png")
await ctx.send(file=discord.File("captcha.png"))
os.system("rm captcha.png")
img.close()
import functools, youtube_dl
#bot.voice_chan = await ctx.author.voice.channel.connect()
bot.voice_chan.stop()
opts = {"format": 'webm[abr>0]/bestaudio/best',"ignoreerrors": True,"default_search": "auto","source_address": "0.0.0.0",'quiet': True}
ydl = youtube_dl.YoutubeDL(opts)
url = 'https://www.youtube.com/watch?v=hjbPszSt5Pc'
func = functools.partial(ydl.extract_info, url, download=False)
info = func()
#bot.voice_chan.play(discord.FFmpegPCMAudio('dead_puppies.mp3'))
bot.voice_chan.play(discord.FFmpegPCMAudio(info['url']))
#async while bot.voice_chan.is_playing():
# pass
#await bot.voice_chan.disconnect()
-64
View File
@@ -1,64 +0,0 @@
import discord
from discord.ext import commands
import logging
from src.imports.utils import Paginator, run_command, Book
import asyncio
owner_id = 351794468870946827
embed_color = discord.Colour.from_rgb(49, 107, 111)
git_log = logging.getLogger('git')
class Git:
def __init__(self, bot):
self.bot = bot
@commands.group(case_insensitive=True, invoke_without_command=True)
async def git(self, ctx):
"""Shows my Git link"""
em = discord.Embed(style='rich',
title=f'Here is where you can find my code',
url='https://github.com/dustinpianalto/Geeksbot/tree/development',
description='I am the development branch of Geeksbot. You can find the master branch here:\n'
'https://github.com/dustinpianalto/Geeksbot/',
color=embed_color)
em.set_thumbnail(url=f'{ctx.guild.me.avatar_url}')
await ctx.send(embed=em)
@git.command()
@commands.is_owner()
async def pull(self, ctx):
"""Pulls updates from GitHub rebasing branch."""
pag = Paginator(self.bot, max_line_length=44, embed=True)
pag.set_embed_meta(title='Git Pull',
color=self.bot.embed_color,
thumbnail=f'{ctx.guild.me.avatar_url}')
pag.add('\uFFF6' + await asyncio.wait_for(self.bot.loop.create_task(run_command('git fetch --all')), 120))
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command('git reset --hard '
'origin/$(git '
'rev-parse --symbolic-full-name'
' --abbrev-ref HEAD)')), 120))
pag.add('\uFFF7\n\uFFF8')
pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command('git show --stat | '
'sed "s/.*@.*[.].*/ /g"')), 10))
book = Book(pag, (None, ctx.channel, self.bot, ctx.message))
await book.create_book()
@git.command()
@commands.is_owner()
async def status(self, ctx):
"""Gets status of current branch."""
pag = Paginator(self.bot, max_line_length=44, max_lines=30, embed=True)
pag.set_embed_meta(title='Git Status',
color=self.bot.embed_color,
thumbnail=f'{ctx.guild.me.avatar_url}')
result = await asyncio.wait_for(self.bot.loop.create_task(run_command('git status')), 10)
pag.add(result)
book = Book(pag, (None, ctx.channel, self.bot, ctx.message))
await book.create_book()
def setup(bot):
bot.add_cog(Git(bot))
View File
-449
View File
@@ -1,449 +0,0 @@
from io import StringIO
import sys
import asyncio
import discord
from discord.ext.commands.formatter import Paginator as DannyPag
from src.imports import checks
import re
import typing
from datetime import datetime
class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stdout = self._stdout
async def mute(bot, ctx, admin=0, member_id=None):
mute_role = bot.db_con.fetchval(f'select muted_role from guild_config where guild_id = $1', ctx.guild.id)
if mute_role:
if admin or await checks.is_admin(bot, ctx):
if ctx.guild.me.guild_permissions.manage_roles:
if member_id:
ctx.guild.get_member(member_id).edit(roles=[discord.utils.get(ctx.guild.roles, id=mute_role)])
def to_list_of_str(items, out: list=list(), level=1, recurse=0):
# noinspection PyShadowingNames
def rec_loop(item, key, out, level):
quote = '"'
if type(item) == list:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}[')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}]')
elif type(item) == dict:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{{')
new_level = level + 1
out = to_list_of_str(item, out, new_level, 1)
out.append(f'{" "*level}}}')
else:
out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{repr(item)},')
if type(items) == list:
if not recurse:
out = list()
out.append('[')
for item in items:
rec_loop(item, None, out, level)
if not recurse:
out.append(']')
elif type(items) == dict:
if not recurse:
out = list()
out.append('{')
for key in items:
rec_loop(items[key], key, out, level)
if not recurse:
out.append('}')
return out
def format_output(text):
if type(text) == list:
text = to_list_of_str(text)
elif type(text) == dict:
text = to_list_of_str(text)
return text
def replace_text_ignorecase(in_str: str, old: str, new: str='') -> str:
re_replace = re.compile(re.escape(old), re.IGNORECASE)
return re_replace.sub(f'{new}', in_str)
async def run_command(args):
# Create subprocess
process = await asyncio.create_subprocess_shell(
args,
# stdout must a pipe to be accessible as process.stdout
stdout=asyncio.subprocess.PIPE)
# Wait for the subprocess to finish
stdout, stderr = await process.communicate()
# Return stdout
return stdout.decode().strip()
class Paginator:
def __init__(self,
bot: discord.ext.commands.Bot,
*,
max_chars: int=1970,
max_lines: int=20,
prefix: str='```md',
suffix: str='```',
page_break: str='\uFFF8',
field_break: str='\uFFF7',
field_name_char: str='\uFFF6',
inline_char: str='\uFFF5',
max_line_length: int=100,
embed=False):
_max_len = 6000 if embed else 1980
assert 0 < max_lines <= max_chars
assert 0 < max_line_length < 120
self._parts = list()
self._prefix = prefix
self._suffix = suffix
self._max_chars = max_chars if max_chars + len(prefix) + len(suffix) + 2 <= _max_len \
else _max_len - len(prefix) - len(suffix) - 2
self._max_lines = max_lines - (prefix + suffix).count('\n') + 1
self._page_break = page_break
self._max_line_length = max_line_length
self._pages = list()
self._max_field_chars = 1014
self._max_field_name = 256
self._max_description = 2048
self._embed = embed
self._field_break = field_break
self._field_name_char = field_name_char
self._inline_char = inline_char
self._embed_title = ''
self._embed_description = ''
self._embed_color = None
self._embed_thumbnail = None
self._embed_url = None
self._bot = bot
def set_embed_meta(self, title: str='\uFFF0',
description: str='\uFFF0',
color: discord.Colour=None,
thumbnail: str=None,
url: str=None):
if len(title) <= self._max_field_name:
self._embed_title = title
else:
raise RuntimeError('Provided Title is too long')
if len(description) <= self._max_description:
self._embed_description = description
else:
raise RuntimeError('Provided Description is too long')
self._embed_color = color
self._embed_thumbnail = thumbnail
self._embed_url = url
def pages(self) -> typing.List[str]:
_pages = list()
_fields = list()
_page = ''
_lines = 0
_field_name = ''
_field_value = ''
_inline = False
def open_page():
nonlocal _page, _lines, _fields
if not self._embed:
_page = self._prefix
_lines = 0
else:
_fields = list()
def close_page():
nonlocal _page, _lines, _fields
if not self._embed:
_page += self._suffix
_pages.append(_page)
else:
if _fields:
_pages.append(_fields)
open_page()
open_page()
if not self._embed:
for part in [str(p) for p in self._parts]:
if part == self._page_break:
close_page()
new_chars = len(_page) + len(part)
if new_chars > self._max_chars:
close_page()
elif (_lines + (part.count('\n') + 1 or 1)) > self._max_lines:
close_page()
_lines += (part.count('\n') + 1 or 1)
_page += '\n' + part
else:
def open_field(name: str):
nonlocal _field_value, _field_name
_field_name = name
_field_value = self._prefix
def close_field(next_name: str=None):
nonlocal _field_name, _field_value, _fields
_field_value += self._suffix
if _field_value != self._prefix + self._suffix:
_fields.append({'name': _field_name, 'value': _field_value, 'inline': _inline})
if next_name:
open_field(next_name)
open_field('\uFFF0')
for part in [str(p) for p in self._parts]:
if part == self._page_break:
close_page()
continue
elif part == self._field_break:
if len(_fields) + 1 < 25:
close_field(next_name='\uFFF0')
else:
close_field()
close_page()
continue
if part.startswith(self._field_name_char):
part = part.replace(self._field_name_char, '')
if part.startswith(self._inline_char):
_inline = True
part = part.replace(self._inline_char, '')
else:
_inline = False
if _field_value and _field_value != self._prefix:
close_field(part)
else:
_field_name = part
continue
_field_value += '\n' + part
close_field()
close_page()
self._pages = _pages
return _pages
def process_pages(self) -> typing.List[str]:
_pages = self._pages or self.pages()
_len_pages = len(_pages)
_len_page_str = len(f'{_len_pages}/{_len_pages}')
if not self._embed:
for i, page in enumerate(_pages):
if len(page) + _len_page_str <= 2000:
_pages[i] = f'{i + 1}/{_len_pages}\n{page}'
else:
for i, page in enumerate(_pages):
em = discord.Embed(title=self._embed_title,
description=self._embed_description,
color=self._bot.embed_color,
)
if self._embed_thumbnail:
em.set_thumbnail(url=self._embed_thumbnail)
if self._embed_url:
em.url = self._embed_url
if self._embed_color:
em.color = self._embed_color
em.set_footer(text=f'{i + 1}/{_len_pages}')
for field in page:
em.add_field(name=field['name'], value=field['value'], inline=field['inline'])
_pages[i] = em
return _pages
def __len__(self):
return sum(len(p) for p in self._parts)
def __eq__(self, other):
# noinspection PyProtectedMember
return self.__class__ == other.__class__ and self._parts == other._parts
def add_page_break(self, *, to_beginning: bool=False) -> None:
self.add(self._page_break, to_beginning=to_beginning)
def add(self, item: typing.Any, *, to_beginning: bool=False, keep_intact: bool=False, truncate=False) -> None:
item = str(item)
i = 0
if not keep_intact and not item == self._page_break:
item_parts = item.strip('\n').split('\n')
for part in item_parts:
if len(part) > self._max_line_length:
if not truncate:
length = 0
out_str = ''
def close_line(line):
nonlocal i, out_str, length
self._parts.insert(i, out_str) if to_beginning else self._parts.append(out_str)
i += 1
out_str = line + ' '
length = len(out_str)
bits = part.split(' ')
for bit in bits:
next_len = length + len(bit) + 1
if next_len <= self._max_line_length:
out_str += bit + ' '
length = next_len
elif len(bit) > self._max_line_length:
if out_str:
close_line(line='')
for out_str in [bit[i:i + self._max_line_length]
for i in range(0, len(bit), self._max_line_length)]:
close_line('')
else:
close_line(bit)
close_line('')
else:
line = f'{part:.{self._max_line_length-3}}...'
self._parts.insert(i, line) if to_beginning else self._parts.append(line)
else:
self._parts.insert(i, part) if to_beginning else self._parts.append(part)
i += 1
elif keep_intact and not item == self._page_break:
if len(item) >= self._max_chars or item.count('\n') > self._max_lines:
raise RuntimeError('{item} is too long to keep on a single page and is marked to keep intact.')
if to_beginning:
self._parts.insert(0, item)
else:
self._parts.append(item)
else:
if to_beginning:
self._parts.insert(0, item)
else:
self._parts.append(item)
class Book:
def __init__(self, pag: Paginator, ctx: typing.Tuple[typing.Optional[discord.Message],
discord.TextChannel,
discord.ext.commands.Bot,
discord.Message]) -> None:
self._pages = pag.process_pages()
self._len_pages = len(self._pages)
self._current_page = 0
self._message, self._channel, self._bot, self._calling_message = ctx
self._locked = True
if pag == Paginator(self._bot):
raise RuntimeError('Cannot create a book out of an empty Paginator.')
def advance_page(self) -> None:
self._current_page += 1
if self._current_page >= self._len_pages:
self._current_page = 0
def reverse_page(self) -> None:
self._current_page += -1
if self._current_page < 0:
self._current_page = self._len_pages - 1
async def display_page(self) -> None:
if isinstance(self._pages[self._current_page], discord.Embed):
if self._message:
await self._message.edit(content=None, embed=self._pages[self._current_page])
else:
self._message = await self._channel.send(embed=self._pages[self._current_page])
else:
if self._message:
await self._message.edit(content=self._pages[self._current_page], embed=None)
else:
self._message = await self._channel.send(self._pages[self._current_page])
async def create_book(self) -> None:
# noinspection PyUnresolvedReferences
async def reaction_checker():
# noinspection PyShadowingNames
def check(reaction, user):
if self._locked:
return str(reaction.emoji) in self._bot.book_emojis.values() \
and user == self._calling_message.author \
and reaction.message.id == self._message.id
else:
return str(reaction.emoji) in self._bot.book_emojis.values() \
and reaction.message.id == self._message.id
await self.display_page()
if len(self._pages) > 1:
for emoji in self._bot.book_emojis.values():
try:
await self._message.add_reaction(emoji)
except (discord.Forbidden, KeyError):
pass
else:
try:
await self._message.add_reaction(self._bot.book_emojis['unlock'])
await self._message.add_reaction(self._bot.book_emojis['close'])
except (discord.Forbidden, KeyError):
pass
while True:
try:
reaction, user = await self._bot.wait_for('reaction_add', timeout=60, check=check)
except asyncio.TimeoutError:
try:
await self._message.clear_reactions()
except discord.Forbidden:
pass
raise asyncio.CancelledError
else:
await self._message.remove_reaction(reaction, user)
if str(reaction.emoji) == self._bot.book_emojis['close']:
await self._calling_message.delete()
await self._message.delete()
raise asyncio.CancelledError
elif str(reaction.emoji) == self._bot.book_emojis['forward']:
self.advance_page()
elif str(reaction.emoji) == self._bot.book_emojis['back']:
self.reverse_page()
elif str(reaction.emoji) == self._bot.book_emojis['end']:
self._current_page = self._len_pages - 1
elif str(reaction.emoji) == self._bot.book_emojis['start']:
self._current_page = 0
elif str(reaction.emoji) == self._bot.book_emojis['hash']:
m = await self._channel.send(f'Please enter a number in range 1 to {self._len_pages}')
def num_check(message):
if self._locked:
return message.content.isdigit() \
and 0 < int(message.content) <= self._len_pages \
and message.author == self._calling_message.author
else:
return message.content.isdigit() \
and 0 < int(message.content) <= self._len_pages
try:
msg = await self._bot.wait_for('message', timeout=30, check=num_check)
except asyncio.TimeoutError:
await m.edit(content='Message Timed out.')
else:
self._current_page = int(msg.content) - 1
try:
await m.delete()
await msg.delete()
except discord.Forbidden:
pass
elif str(reaction.emoji) == self._bot.book_emojis['unlock']:
self._locked = False
await self._message.remove_reaction(reaction, self._channel.guild.me)
continue
await self.display_page()
self._bot.loop.create_task(reaction_checker())
View File
-23
View File
@@ -1,23 +0,0 @@
import asyncpg
import asyncio
class DatabaseConnection:
def __init__(self, host: str='localhost', port: int=5432, database: str='', user: str='', password: str=''):
if user == '' or password == '' or database == '':
raise RuntimeError('Username or Password are blank')
self.kwargs = {'host': host, 'port': port, 'database': database, 'user': user, 'password': password}
self._conn = None
asyncio.get_event_loop().run_until_complete(self.acquire())
self.fetchval = self._conn.fetchval
self.execute = self._conn.execute
self.fetch = self._conn.fetch
self.fetchrow = self._conn.fetchrow
async def acquire(self):
if not self._conn:
self._conn = await asyncpg.create_pool(**self.kwargs)
async def close(self):
await self._conn.close()
self._conn = None