aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorromangraef <romangraef@loves.dicksinhisan.us>2018-08-21 16:12:39 +0200
committerromangraef <romangraef@loves.dicksinhisan.us>2018-08-21 16:12:39 +0200
commita4c058b5c0ec4e441c63ae2ef15235a4869d2bac (patch)
tree64a039e55976adace41fb56d4fc77e88333db802
downloadnotaselfbotv2finalforsure-a4c058b5c0ec4e441c63ae2ef15235a4869d2bac.tar.gz
notaselfbotv2finalforsure-a4c058b5c0ec4e441c63ae2ef15235a4869d2bac.tar.bz2
notaselfbotv2finalforsure-a4c058b5c0ec4e441c63ae2ef15235a4869d2bac.zip
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--config.py19
-rw-r--r--copypasta/flextape.txt1
-rw-r--r--copypasta/gnulinux.txt5
-rw-r--r--copypasta/loss.txt2
-rw-r--r--copypasta/uwot.txt1
-rwxr-xr-xlaunch.sh8
-rw-r--r--main.py14
-rw-r--r--modules/admin.py126
-rw-r--r--modules/binary.py27
-rw-r--r--modules/converters.py42
-rw-r--r--modules/copypasta.py27
-rw-r--r--modules/dump.py78
-rw-r--r--modules/github.py68
-rw-r--r--modules/pypi.py48
-rw-r--r--modules/ragequit.py17
-rw-r--r--requirements.txt5
-rw-r--r--utils.py10
18 files changed, 499 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f733c4b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+config/
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..7e2b89a
--- /dev/null
+++ b/config.py
@@ -0,0 +1,19 @@
+from cached_property import cached_property
+from configlib import BaseConfig
+from github import Github
+
+
+class GithubConfig(object):
+ access_token: str
+
+ @cached_property
+ def github(self):
+ return Github(self.access_token)
+
+
+class Config(BaseConfig):
+ token: str
+ github: GithubConfig
+
+
+config = Config.get_instance()
diff --git a/copypasta/flextape.txt b/copypasta/flextape.txt
new file mode 100644
index 0000000..b78ef54
--- /dev/null
+++ b/copypasta/flextape.txt
@@ -0,0 +1 @@
+Hi, Phil Swift here with Flex Tape! The super-strong waterproof tape! That can instantly patch, bond, seal, and repair! Flex tape is no ordinary tape; its triple thick adhesive virtually welds itself to the surface, instantly stopping the toughest leaks. Leaky pipes can cause major damage, but Flex Tape grips on tight and bonds instantly! Plus, Flex Tape’s powerful adhesive is so strong, it even works underwater! Now you can repair leaks in pools and spas in water without draining them! Flex Tape is perfect for marine, campers and RVs! Flex Tape is super strong, and once it's on, it holds on tight! And for emergency auto repair, Flex Tape keeps its grip, even in the toughest conditions! Big storms can cause big damage, but Flex Tape comes super wide, so you can easily patch large holes. To show the power of Flex Tape, I sawed this boat in half! And repaired it with only Flex Tape! Not only does Flex Tape’s powerful adhesive hold the boat together, but it creates a super strong water tight seal, so the inside is completly dry! Yee-doggy! Just cut, peel, stick and seal! Imagine everything you can do with the power of Flex Tape!
diff --git a/copypasta/gnulinux.txt b/copypasta/gnulinux.txt
new file mode 100644
index 0000000..4530d7a
--- /dev/null
+++ b/copypasta/gnulinux.txt
@@ -0,0 +1,5 @@
+I'd just like to interject for moment. What you're refering to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX.
+
+Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called Linux, and many of its users are not aware that it is basically the GNU system, developed by the GNU Project.
+
+There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called Linux distributions are really distributions of GNU/Linux! \ No newline at end of file
diff --git a/copypasta/loss.txt b/copypasta/loss.txt
new file mode 100644
index 0000000..04fda0f
--- /dev/null
+++ b/copypasta/loss.txt
@@ -0,0 +1,2 @@
+✋Excuse me sir ✋👏but 👏👉is that original post you made 👉right there 👉loss ❓☝Now hold on ☝😡it might sound ridiculous 😡😤but bare with me here. 😤👀You see 👀 there's 4️⃣ panels ☝let's count them ☝ 1️⃣ 2️⃣ 3️⃣ 4️⃣ panels ❗️❗️✋And you know what else has 4️⃣ panels ❓😤That's right 😤😡loss does ❗️😡 👇But i'm not done yet 👇 👀you see 👀👉in the first panel 👉☝there is ☝ 1️⃣ object 👈 positioned slightly to the left. 👈 😡Should I even continue ❓😡😤I guess I will 😤😒as you still don't understand. 😒 😲I should clarify this is a level 5 loss meme 😲🙄so I don't expect you to understand it. 🙄 💁\u200d Anyways 💁\u200d ✌️ in the second panel ✌️👀there are 2️⃣ objects 👀👉next to each other 👉 👇with one being slightly below the other. 👇☝ In the 3️⃣rd panel ☝ ✌️another 2️⃣ objects are present ✌️ 🙌right next to each other. 🙌 👆 Finally, 👆 there are, yet again, 2️⃣ objects 👆 🤙 which form an L shape. 🤙 👀Everything looks like it's adding up 👀😤therefore😤😡it HAS to be loss ❗️❗️😒You need to make it less obvious next time 😒🙄if you want it to be more funny. 🙄
+
diff --git a/copypasta/uwot.txt b/copypasta/uwot.txt
new file mode 100644
index 0000000..0d0d49b
--- /dev/null
+++ b/copypasta/uwot.txt
@@ -0,0 +1 @@
+What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills. I am trained in gorilla warfare and I'm the top sniper in the entire US armed forces. You are nothing to me but just another target. I will wipe you the fuck out with precision the likes of which has never been seen before on this Earth, mark my fucking words. You think you can get away with saying that shit to me over the Internet? Think again, fucker. As we speak I am contacting my secret network of spies across the USA and your IP is being traced right now so you better prepare for the storm, maggot. The storm that wipes out the pathetic little thing you call your life. You're fucking dead, kid. I can be anywhere, anytime, and I can kill you in over seven hundred ways, and that's just with my bare hands. Not only am I extensively trained in unarmed combat, but I have access to the entire arsenal of the United States Marine Corps and I will use it to its full extent to wipe your miserable ass off the face of the continent, you little shit. If only you could have known what unholy retribution your little "clever" comment was about to bring down upon you, maybe you would have held your fucking tongue. But you couldn't, you didn't, and now you're paying the price, you goddamn idiot. I will shit fury all over you and you will drown in it. You're fucking dead, kiddo. \ No newline at end of file
diff --git a/launch.sh b/launch.sh
new file mode 100755
index 0000000..dd26e56
--- /dev/null
+++ b/launch.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+scriptdir=$(dirname $(readlink -f $0))
+cd ${scriptdir}
+while true
+do
+ `pwd`/venv/bin/python main.py &>$HOME/logs/discordbot
+done
+
diff --git a/main.py b/main.py
new file mode 100644
index 0000000..62da318
--- /dev/null
+++ b/main.py
@@ -0,0 +1,14 @@
+from discord.ext.commands import Bot, when_mentioned_or
+
+from config import config
+from utils import load_all_modules
+
+bot = Bot(
+ command_prefix=when_mentioned_or('~'),
+ self_bot=True,
+)
+
+load_all_modules(bot)
+
+if __name__ == '__main__':
+ bot.run(config.token, bot=False)
diff --git a/modules/admin.py b/modules/admin.py
new file mode 100644
index 0000000..fb50a2e
--- /dev/null
+++ b/modules/admin.py
@@ -0,0 +1,126 @@
+import asyncio
+import os
+import re
+from typing import List, Dict, Pattern
+
+import discord
+from discord import Embed, Color, Message
+from discord.ext import commands
+from discord.ext.commands import Context as CommandContext, Bot
+
+from config import config
+from utils import load_all_modules
+
+REPLACEMENTS: Dict[Pattern, str] = {
+ re.compile(r'<@!?(?P<id>[0-9]+)>'): '(guild.get_member({id}) if guild is not None else client.get_user({id}))',
+ re.compile(r'<#(?P<id>[0-9]+)>'): '(discord.utils.get(all_channels, id={id}))',
+ re.compile(r'<@&(?P<id>[0-9]+)>'): '(discord.utils.get(all_roles, id={id}))',
+ # Maybe later emoji support
+}
+
+
+async def handle_eval(message: discord.Message, client: discord.Client, to_eval: str):
+ channel: discord.TextChannel = message.channel
+ author: discord.Member = message.author
+
+ all_channels: List[discord.Guild] = []
+ all_roles: List[discord.Role] = []
+ for guild in client.guilds:
+ guild: discord.Guild = guild # for type hints
+ all_channels += guild.channels
+ all_roles += guild.roles
+
+ variables = {
+ 'message': message,
+ 'author': author,
+ 'channel': channel,
+ 'all_channels': all_channels,
+ 'all_roles': all_roles,
+ 'client': client,
+ 'discord': discord,
+ 'os': os,
+ 'github': config.github.github,
+ 'print': (lambda *text: client.loop.create_task(channel.send(' '.join(text)))),
+ 'guild': channel.guild if hasattr(channel, 'guild') else None,
+ }
+ lines: List[str] = to_eval.strip().split('\n')
+ lines[-1] = 'return ' + lines[-1]
+ block: str = '\n'.join(' ' + line for line in lines)
+ code = f"async def code({', '.join(variables.keys())}):\n" \
+ f"{block}"
+
+ for regex, replacement in REPLACEMENTS.items():
+ code = re.sub(regex, lambda match: replacement.format(**match.groupdict()), code)
+
+ _globals, _locals = {}, {}
+ try:
+ exec(code, _globals, _locals)
+ except Exception as e:
+ await message.channel.send(
+ embed=discord.Embed(color=discord.Color.red(), description="Compiler Error: `%s`" % (str(e))))
+ return
+ result = {**_globals, **_locals}
+ try:
+ result = await result["code"](**variables)
+ except Exception as e:
+ await message.channel.send(
+ embed=discord.Embed(color=discord.Color.red(), description="Runtime Error: `%s`" % (str(e))))
+ return
+
+ return await channel.send(
+ embed=Embed(
+ color=Color.red(),
+ description="📥 Evaluation success: ```py\n%r\n```" % result))
+
+
+class AdminCog(object):
+ def __init__(self, bot: commands.Bot):
+ self.bot: commands.Bot = bot
+
+ # noinspection PyMethodMayBeStatic
+ async def on_ready(self):
+ print('Logged in.')
+
+ @commands.command()
+ async def eval(self, ctx: CommandContext, *, to_eval: str = None):
+ if to_eval is None:
+ return await ctx.send(
+ embed=Embed(
+ description="<Insert generic insult about your stupidity here>",
+ color=Color.red()))
+ await handle_eval(ctx.message, self.bot, to_eval)
+
+ @commands.command()
+ async def reload(self, ctx: CommandContext, *extensions):
+ for extension in (extensions or self.bot.extensions.copy().keys()):
+ self.bot.unload_extension(extension)
+ messages: List[Message] = [
+ await ctx.send(
+ embed=Embed(
+ color=Color.red(),
+ description='Unloaded extensions')),
+ ctx.message]
+ if len(extensions) == 0:
+ load_all_modules(self.bot)
+ else:
+ for extension in extensions:
+ try:
+ self.bot.load_extension(extension)
+ except:
+ messages.append(
+ await ctx.send(
+ embed=Embed(
+ title=f"Failed to load module `{extension}`",
+ color=Color.red())))
+ messages.append(
+ await ctx.send(
+ embed=Embed(
+ title=f"Reloaded {len(extensions) or len(self.bot.extensions)} extension(s)",
+ color=Color.green())))
+ await asyncio.sleep(10)
+ for mes in messages:
+ await mes.delete()
+
+
+def setup(bot: Bot):
+ bot.add_cog(AdminCog(bot))
diff --git a/modules/binary.py b/modules/binary.py
new file mode 100644
index 0000000..70ed010
--- /dev/null
+++ b/modules/binary.py
@@ -0,0 +1,27 @@
+from discord import Message
+from discord.ext.commands import Bot, command, Context
+
+
+class BinaryCog(object):
+ def __init__(self, bot: Bot):
+ self.bot: Bot = bot
+
+ @command()
+ async def binary(self, ctx: Context, *, text: str):
+ binary = ' '.join(bin(ord(ch))[2:].zfill(8) for ch in text)
+ mes: Message = ctx.message
+ await mes.edit(content=binary)
+
+ @command()
+ async def un_binary(self, ctx: Context, message: Message):
+ text: str = message.content
+ print(text)
+ text.replace(' ', '').replace('\n', '')
+ chunks, chunk_size = len(text), len(text) // 4
+ await ctx.message.edit(
+ content=f"Decoded message {message.jump_url}: "
+ f"{''.join([chr(int(text[i:i + chunk_size], 2)) for i in range(0, chunks, chunk_size)])}")
+
+
+def setup(bot: Bot):
+ bot.add_cog(BinaryCog(bot))
diff --git a/modules/converters.py b/modules/converters.py
new file mode 100644
index 0000000..f86ff59
--- /dev/null
+++ b/modules/converters.py
@@ -0,0 +1,42 @@
+from string import digits
+
+from discord import TextChannel
+from discord.ext.commands import Converter, Context, Bot, BadArgument, converter
+
+
+def is_int(text):
+ return all(map(digits.__contains__, text))
+
+
+class GuildConverter(Converter):
+ async def convert(self, ctx: Context, argument):
+ bot: Bot = ctx.bot
+ try:
+ return bot.get_guild(int(argument))
+ except:
+ try:
+ return [guild for guild in bot.guilds if guild.name.casefold() == argument.casefold()][0]
+ except:
+ raise BadArgument(f"Could not find guild with id or name {argument}")
+
+
+class MessageConverter(Converter):
+ async def convert(self, ctx: Context, argument: str):
+ bot: Bot = ctx.bot
+ if is_int(argument):
+ message = int(argument)
+ channel: TextChannel = ctx.channel
+ else:
+ _, channel, message = list(map(int, filter(is_int, argument.split('/'))))
+ channel: TextChannel = bot.get_channel(channel)
+
+ if bot.user.bot:
+ return await channel.get_message(message)
+ else:
+ return (await channel.history(around=message, limit=2).flatten())[1]
+
+
+# noinspection PyUnusedLocal
+def setup(bot: Bot):
+ converter.MessageConverter = MessageConverter
+ converter.GuildConverter = GuildConverter
diff --git a/modules/copypasta.py b/modules/copypasta.py
new file mode 100644
index 0000000..4cb747f
--- /dev/null
+++ b/modules/copypasta.py
@@ -0,0 +1,27 @@
+from _contextvars import Context
+from string import digits, ascii_uppercase, ascii_lowercase
+
+from discord.ext.commands import Bot, command, Converter
+
+
+class CopyPastaTextConverter(Converter):
+
+ async def convert(self, ctx: Context, argument: str):
+ allowed_characters = ascii_uppercase + ascii_lowercase + digits + '_- '
+ if any(ch not in allowed_characters for ch in argument):
+ return
+ with open(f'copypasta/{argument}.txt') as fp:
+ return fp.read().strip()
+
+
+class CopyPasta(object):
+ def __init__(self, bot: Bot):
+ self.bot: Bot = bot
+
+ @command(aliases=['cp', 'copy-pasta', 'copypasta'])
+ async def copy_pasta(self, ctx: Context, copy_pasta: CopyPastaTextConverter):
+ await ctx.message.edit(content=str(copy_pasta))
+
+
+def setup(bot: Bot):
+ bot.add_cog(CopyPasta(bot))
diff --git a/modules/dump.py b/modules/dump.py
new file mode 100644
index 0000000..5281d7d
--- /dev/null
+++ b/modules/dump.py
@@ -0,0 +1,78 @@
+from typing import List
+
+from discord import User, Embed, Profile, Guild, Member, Permissions, Message
+from discord.ext.commands import Bot, command, Context as CommandContext, Context
+
+
+async def _context_react(self: Context, emoji):
+ await self.message.add_reaction(emoji)
+
+
+Context.react = _context_react
+
+
+def dump_perms(permissions: Permissions):
+ def perm_names():
+ for perm, value in permissions:
+ if value:
+ yield perm
+
+ return ', '.join(perm_names())
+
+
+class DumpCog(object):
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ @command()
+ async def raw(self, ctx: CommandContext, message: Message):
+ content: str = message.content
+ escaped = content.replace('```', '``\u200B`')
+ await ctx.send(content=f'```\n{escaped}\n```')
+ await ctx.react('✅')
+
+ @command()
+ async def user(self, ctx: CommandContext, user: User, guild: Guild = None):
+ if guild is None and ctx.guild is not None:
+ guild: Guild = ctx.guild
+ profile: Profile = await user.profile()
+ description = ""
+ if profile.nitro:
+ description += f"i can haz animated emojis since {profile.premium_since}\n"
+ if profile.hypesquad:
+ description += f"they got some hype\n"
+ if profile.partner:
+ description += "insrt BLU INFINITY SYMBOL her\n"
+ if profile.staff:
+ description += "staff. if this is b1nzy, then FUCK him for banning selfbots\n"
+ mutual: List[Guild] = profile.mutual_guilds
+
+ mutual_text = '\n'.join(guild.name for guild in mutual)
+ if len(mutual_text) > 512:
+ mutual_text = f"Together in {len(mutual)} guilds. [Truncated]"
+
+ em = Embed(
+ title=str(user),
+ description=description,
+ )
+ if guild:
+ member: Member = guild.get_member(user.id)
+ if member:
+ if guild.owner_id == member.id:
+ em.add_field(name="Owner", value="Yeah", inline=True)
+ em.colour = member.color
+ em.add_field(name="Joined Guild", value=member.joined_at, inline=True)
+ em.add_field(name="Permissions", value=dump_perms(member.guild_permissions), inline=True)
+ em.set_author(name=user.display_name, icon_url=user.avatar_url)
+ em.add_field(name="Mutual guilds", value=mutual_text, inline=True)
+ em.add_field(name="Joined Discord", value=user.created_at, inline=True)
+ for connection in profile.connected_accounts:
+ em.add_field(name=connection['type'], value=('☑' if connection['verified'] else '') + connection['name'],
+ inline=True)
+ em.set_thumbnail(url=user.avatar_url)
+ await ctx.send(embed=em)
+ await ctx.react('✅')
+
+
+def setup(bot: Bot):
+ bot.add_cog(DumpCog(bot))
diff --git a/modules/github.py b/modules/github.py
new file mode 100644
index 0000000..cb639dc
--- /dev/null
+++ b/modules/github.py
@@ -0,0 +1,68 @@
+from typing import List
+
+from discord import Embed, Color
+from discord.ext.commands import Bot, Group
+from discord.ext.commands import Context as CommandContext, group
+from github.NamedUser import NamedUser
+from github.Repository import Repository
+
+from config import config
+
+github = config.github.github
+
+
+def find_repo(search):
+ repos: List[Repository] = list(github.get_repos())
+ checks = [
+ lambda r: r.id.lower() == search.lower(),
+ lambda r: search.lower() in r.name.lower(),
+ lambda r: search.lower() in repo.description.lower(),
+ ]
+ for check in checks:
+ for repo in repos:
+ if check(repo):
+ return repo
+
+
+class GithubCog(object):
+ def __init__(self, bot: Bot):
+ self.bot: Bot = bot
+
+ @group(invoke_without_subcommand=True)
+ async def github(self, ctx: CommandContext):
+ pass
+
+ github: Group = github
+
+ @github.command()
+ async def me(self, ctx: CommandContext):
+ user: NamedUser = github.get_user()
+ embed = Embed(
+ color=Color.blurple(),
+ title=user.login,
+ description=user.bio,
+ url=user.url,
+ )
+ embed.add_field(name='Followers', value=user.followers)
+ embed.add_field(name='Following', value=user.following)
+ embed.add_field(name='Repositories',
+ value=f'[{user.public_repos + user.total_private_repos}](https://github.com/{user.login})')
+ embed.set_thumbnail(url=user.avatar_url)
+ await ctx.send(embed=embed)
+
+ @github.command()
+ async def repo(self, ctx: CommandContext, *, search):
+ repo = find_repo(search)
+ print(repo)
+ print(search)
+ if repo:
+ embed = Embed(
+ title=repo.id,
+ description=repo.description,
+ url=repo.html_url
+ )
+ await ctx.send(embed=embed)
+
+
+def setup(bot: Bot):
+ bot.add_cog(GithubCog(bot))
diff --git a/modules/pypi.py b/modules/pypi.py
new file mode 100644
index 0000000..eebd3be
--- /dev/null
+++ b/modules/pypi.py
@@ -0,0 +1,48 @@
+import re
+from subprocess import Popen, PIPE
+from typing import List
+from typing import Pattern
+
+from discord import Message, Embed, Color
+from discord.ext.commands import Bot, Context as CommandContext, command
+
+matcher: Pattern = re.compile(r'(?P<name>\w+)[^(]+\((?P<version>[^)]+)\)[^\-]+(?P<description>.*)')
+
+
+class PyPiCog(object):
+ def __init__(self, bot: Bot):
+ self.bot: Bot = bot
+
+ @command()
+ async def pypi(self, ctx: CommandContext, search, *, text=''):
+ message: Message = ctx.message
+ proc = Popen(['pip', 'search', search], stdout=PIPE, stderr=PIPE)
+ if proc.wait() != 0:
+ await message.edit(
+ embed=Embed(
+ color=Color.red(),
+ description='Failed to search for `{search}` on pypi.'))
+ stdout, _ = proc.communicate()
+ stdout = stdout.decode(encoding='utf-8')
+ lines: List[str] = stdout.split('\n')
+ content = lines[0] + ' '.join(line.strip() for line in lines[1:] if line.startswith(' '))
+ match = matcher.match(content)
+ if not match:
+ await message.edit(
+ embed=Embed(
+ color=Color.dark_orange(),
+ description=f"Weird response format:```\n{stdout[:512]}```"))
+ name = match.group("name")
+ version = match.group("version")
+ description = match.group("description")
+ await message.edit(
+ content=text,
+ embed=Embed(
+ title=name,
+ url=f"https://pypi.org/project/{name}/",
+ description=description
+ ).set_footer(text=version))
+
+
+def setup(bot: Bot):
+ bot.add_cog(PyPiCog(bot))
diff --git a/modules/ragequit.py b/modules/ragequit.py
new file mode 100644
index 0000000..aaf43f8
--- /dev/null
+++ b/modules/ragequit.py
@@ -0,0 +1,17 @@
+from discord import Guild
+from discord.ext.commands import Bot, command, Context as CommandContext, guild_only
+
+
+class RageQuitCog(object):
+ def __init__(self, bot: Bot):
+ self.bot: Bot = bot
+
+ @command()
+ @guild_only()
+ async def ragequit(self, ctx: CommandContext):
+ guild: Guild = ctx.guild
+ await guild.leave()
+
+
+def setup(bot: Bot):
+ bot.add_cog(RageQuitCog(bot))
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..46f2ecc
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+https://github.com/Rapptz/discord.py/archive/rewrite.zip
+https://github.com/aaugustin/websockets/archive/master.zip
+https://github.com/romangraef/configlib/archive/master.zip
+cached-property
+PyGithub
diff --git a/utils.py b/utils.py
new file mode 100644
index 0000000..86f7101
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,10 @@
+import os
+from discord.ext.commands import Bot
+
+
+def load_all_modules(bot: Bot, module_folder='modules', module_package=None):
+ if module_package is None:
+ module_package = module_folder.replace('/', '.')
+ for module in sorted(os.listdir(module_folder)):
+ if module.endswith('.py') and not module.startswith('_'):
+ bot.load_extension(module_package + '.' + module[:-3])