diff --git a/.gitignore b/.gitignore
index 62bf56b..0b40e9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-.env
-env
-__pycache__
+.env
+env
+__pycache__
song.mp3
\ No newline at end of file
diff --git a/README.md b/README.md
index d5ea99b..11b1a75 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,68 @@
-# Duckster
-A fun and all-purpose bot made in Python.
-
-Founded by [@iakrules](https://github.com/iakrules) and [@rzmk](https://github.com/rzmk).
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+# Rutgers Esports Discord Bot
+
+The official **Rutgers Esports Bot** on Discord for handling all internal operations.
+
+Built as a modular multi-purpose utility bot, with custom features such as creating embeds, moderation commands, and more.
+
+## Table of Contents
+
+- [About The Project](#about-the-project)
+- [Tools & Technologies](#tools-and-technologies)
+- [Contributing](#contributing)
+- [Contributors](#contributors)
+
+## About The Project
+
+[](https://discord.gg/RutgersEsports)
+
+Rutgers Esports Bot is developed for ease of use with utility commands and features for gaming communities, clubs, and organizations.
+
+- **Community** - Members can easily access informative commands about their user/server, and also details about Rutgers Esports such as upcoming events.
+- **Moderation** - Built-in moderation commands are automatically provided and limited to server members with specific permissions.
+- **Executive Board** - Restricted features providing convenient automation and information to serve board members spanning all departments.
+
+## Tools and Technologies
+
+- [Discord.py](https://discordpy.readthedocs.io/en/stable/) - Modern async/await API wrapper for Discord.
+- [Heroku](https://www.heroku.com/) - Hosting and deployment cloud platform.
+- [Firestore DB](https://firebase.google.com/) - Realtime NoSQL database for managing our inventory.
+- [Google Calendar API](https://developers.google.com/calendar) - For instant reports of upcoming calendar events.
+
+## Contributing
+
+There's plenty of ways to contribute!
+Here's a few suggestions to help:
+
+1. Star the project.
+2. Find and report [issues](https://github.com/rutgersesports/discord-bot/issues).
+3. Submit [PRs](https://github.com/rutgersesports/discord-bot/pulls) to help solve issues or add features.
+4. Send feature requests [our Discord](https://discord.gg/RutgersEsports)!
+
+We'll be creating a contributing guide/wiki in the near future.
+
+For now, Rutgers Esports Technology Department officers will be maintaining the bot. [Click here to apply.](https://bit.ly/join-reebo)
+
+## Contributors
+
+This project exists thanks to all the people who contribute!
+
+
+
+
diff --git a/bot.py b/bot.py
index c3587cc..ba4569d 100644
--- a/bot.py
+++ b/bot.py
@@ -1,47 +1,35 @@
-# Import all required packages and variables
-import os
-import discord
-from discord.ext import commands
-from dotenv import load_dotenv
-
-load_dotenv()
-
-TOKEN = os.environ.get("TOKEN")
-client = commands.Bot(command_prefix='d!')
-
-# Bot initialized
-@client.event
-async def on_ready():
- print(f'{client.user.name} is ready.')
- await client.change_presence(activity=discord.Streaming(name="duck pictures.", url="https://www.youtube.com/watch?v=dQw4w9WgXcQ"))
-
-@commands.is_owner()
-@client.command()
-async def load(ctx, extension):
- """Loads a cog"""
- client.load_extension(f'cogs.{extension}')
-
-@commands.is_owner()
-@client.command()
-async def unload(ctx, extension):
- """Unloads a cog"""
- client.unload_extension(f'cogs.{extension}')
-
-# Cogs
-for filename in os.listdir('./cogs/info'):
- if filename.endswith('.py'):
- client.load_extension(f'cogs.info.{filename[:-3]}')
-
-for filename in os.listdir('./cogs/moderation'):
- if filename.endswith('.py'):
- client.load_extension(f'cogs.moderation.{filename[:-3]}')
-
-for filename in os.listdir('./cogs/music'):
- if filename.endswith('.py'):
- client.load_extension(f'cogs.music.{filename[:-3]}')
-
-for filename in os.listdir('./cogs/inventory'):
- if filename.endswith('.py'):
- client.load_extension(f'cogs.inventory.{filename[:-3]}')
-
-client.run(TOKEN)
\ No newline at end of file
+# Import all required packages and variables
+import os
+import discord
+from discord.ext import commands
+from dotenv import load_dotenv
+
+load_dotenv()
+
+intents = discord.Intents().all()
+
+TOKEN = os.environ.get("TOKEN")
+bot = commands.Bot(command_prefix='.', intents=intents)
+
+# Bot initialized
+@bot.event
+async def on_ready():
+ print(f'{bot.user.name} is ready.')
+ await bot.change_presence(activity=discord.Streaming(name="Use .help to learn more!", url="https://linktr.ee/RutgersEsports"))
+
+class CustomHelpCommand(commands.MinimalHelpCommand):
+ async def send_pages(self):
+ destination = self.get_destination()
+ for page in self.paginator.pages:
+ embed = discord.Embed(description=page, color=0xC94949)
+ await destination.send(embed=embed)
+
+bot.help_command = CustomHelpCommand()
+
+# Cogs
+for group in os.listdir('./cogs'):
+ for cog in os.listdir(f'./cogs/{group}'):
+ if cog.endswith('.py'):
+ bot.load_extension(f'cogs.{group}.{cog[:-3]}')
+
+bot.run(TOKEN)
\ No newline at end of file
diff --git a/cogs/music/musicCog.py b/cogs/audio/audioCog.py
similarity index 90%
rename from cogs/music/musicCog.py
rename to cogs/audio/audioCog.py
index 8be0488..848c250 100644
--- a/cogs/music/musicCog.py
+++ b/cogs/audio/audioCog.py
@@ -1,162 +1,161 @@
-import asyncio
-import discord
-from discord.ext import commands
-import youtube_dl
-import os
-
-ffmpeg_options = {
- 'options': '-vn'
-}
-
-ytdl_format_options = {
- 'format': 'bestaudio/best',
- 'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
- 'restrictfilenames': True,
- 'noplaylist': True,
- 'nocheckcertificate': True,
- 'ignoreerrors': False,
- 'logtostderr': False,
- 'quiet': True,
- 'no_warnings': True,
- 'default_search': 'auto',
- 'source_address': '0.0.0.0' # bind to ipv4 since ipv6 addresses cause issues sometimes
-}
-
-ytdl = youtube_dl.YoutubeDL(ytdl_format_options)
-
-class YTDLSource(discord.PCMVolumeTransformer):
- def __init__(self, source, *, data, volume=0.5):
- super().__init__(source, volume)
-
- self.data = data
-
- self.title = data.get('title')
- self.url = data.get('url')
-
- @classmethod
- async def from_url(cls, url, *, loop=None, stream=False):
- loop = loop or asyncio.get_event_loop()
- data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))
-
- if 'entries' in data:
- # take first item from a playlist
- data = data['entries'][0]
-
- filename = data['url'] if stream else ytdl.prepare_filename(data)
- return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)
-
-class MusicCog(commands.Cog):
-
- def __init__(self, client):
- self.client = client
-
- # Commands
-
- @commands.command()
- async def play(self, ctx, url):
- """Streams from a url"""
-
- if ctx.author.voice is None:
- return await ctx.send("You're not connected to a voice channel.")
-
- try:
- channel = ctx.author.voice.channel
- await channel.connect()
- except:
- pass
- async with ctx.typing():
- player = await YTDLSource.from_url(url, loop=self.client.loop, stream=True)
- ctx.voice_client.play(player, after=lambda e: print(f'Player error: {e}') if e else None)
- await ctx.send(f'Now playing: {player.title}')
-
- @play.error
- async def play_error(self, ctx, error):
- if isinstance(error, commands.MissingRequiredArgument):
- voice = ctx.voice_client
- if voice != None and voice.is_paused():
- voice.resume()
- else:
- await ctx.send("Missing URL!")
-
- @commands.command(aliases=['disconnect, dc'])
- async def leave(self, ctx):
- """Disconnects bot from the voice channel"""
-
- if ctx.author.voice is None:
- return await ctx.send("You're not connected to a voice channel.")
-
- voice = ctx.voice_client
- if voice is not None:
- if voice.is_connected():
- await voice.disconnect()
- else:
- await ctx.send("The bot is not connected to a voice channel.")
-
- @commands.command()
- async def volume(self, ctx, volume: int):
- """Changes the player's volume"""
-
- if ctx.author.voice is None:
- return await ctx.send("You're not connected to a voice channel.")
-
- if ctx.voice_client is None:
- return await ctx.send("Not connected to a voice channel.")
-
- ctx.voice_client.source.volume = volume / 100
- await ctx.send(f"Changed volume to {volume}%")
-
- @commands.command()
- async def pause(self, ctx):
- """Pauses audio"""
-
- if ctx.author.voice is None:
- return await ctx.send("You're not connected to a voice channel.")
-
- voice = ctx.voice_client
- if voice.is_playing():
- voice.pause()
- else:
- await ctx.send("Currently no audio is playing.")
-
- @commands.command()
- async def resume(self, ctx):
- """Resumes currently paused audio"""
-
- if ctx.author.voice is None:
- return await ctx.send("You're not connected to a voice channel.")
-
- voice = ctx.voice_client
- if voice.is_paused():
- voice.resume()
- else:
- await ctx.send("The audio is not paused.")
-
- @commands.command()
- async def stop(self, ctx):
- """Stops and disconnects the bot from voice"""
-
- if ctx.author.voice is None:
- return await ctx.send("You're not connected to a voice channel.")
-
- voice = ctx.voice_client
- if voice:
- voice.stop()
-
- @commands.command(aliases=['join'])
- async def connect(self, ctx):
- """Connects bot to currently connected voice channel"""
-
- if ctx.author.voice is None:
- return await ctx.send("You're not connected to a voice channel.")
-
- channel = ctx.author.voice.channel
- try:
- await channel.connect()
- except:
- voice = ctx.voice_client
- if voice.is_connected() and voice.channel != channel:
- await voice.disconnect()
- await channel.connect()
-
-def setup(client):
- client.add_cog(MusicCog(client))
\ No newline at end of file
+import asyncio
+import discord
+from discord.ext import commands
+import youtube_dl
+import os
+
+ffmpeg_options = {
+ 'options': '-vn'
+}
+
+ytdl_format_options = {
+ 'format': 'bestaudio/best',
+ 'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
+ 'restrictfilenames': True,
+ 'noplaylist': True,
+ 'nocheckcertificate': True,
+ 'ignoreerrors': False,
+ 'logtostderr': False,
+ 'quiet': True,
+ 'no_warnings': True,
+ 'default_search': 'auto',
+ 'source_address': '0.0.0.0' # Bind to ipv4 since ipv6 addresses cause issues sometimes
+}
+
+ytdl = youtube_dl.YoutubeDL(ytdl_format_options)
+
+class YTDLSource(discord.PCMVolumeTransformer):
+ def __init__(self, source, *, data, volume=0.5):
+ super().__init__(source, volume)
+
+ self.data = data
+
+ self.title = data.get('title')
+ self.url = data.get('url')
+
+ @classmethod
+ async def from_url(cls, url, *, loop=None, stream=False):
+ loop = loop or asyncio.get_event_loop()
+ data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))
+
+ if 'entries' in data:
+ # Take first item from a playlist
+ data = data['entries'][0]
+
+ filename = data['url'] if stream else ytdl.prepare_filename(data)
+ return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)
+
+class AudioCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command()
+ async def play(self, ctx, url):
+ """Streams from a url"""
+
+ if ctx.author.voice is None:
+ return await ctx.send("You're not connected to a voice channel.")
+
+ try:
+ channel = ctx.author.voice.channel
+ await channel.connect()
+ except:
+ pass
+ async with ctx.typing():
+ player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
+ ctx.voice_client.play(player, after=lambda e: print(f'Player error: {e}') if e else None)
+ await ctx.send(f'Now playing: {player.title}')
+
+ @play.error
+ async def play_error(self, ctx, error):
+ if isinstance(error, commands.MissingRequiredArgument):
+ voice = ctx.voice_client
+ if voice != None and voice.is_paused():
+ voice.resume()
+ else:
+ await ctx.send("Missing URL!")
+
+ @commands.command(aliases=['disconnect, dc'])
+ async def leave(self, ctx):
+ """Disconnects bot from the voice channel"""
+
+ if ctx.author.voice is None:
+ return await ctx.send("You're not connected to a voice channel.")
+
+ voice = ctx.voice_client
+ if voice is not None:
+ if voice.is_connected():
+ await voice.disconnect()
+ else:
+ await ctx.send("The bot is not connected to a voice channel.")
+
+ @commands.command()
+ async def volume(self, ctx, volume: int):
+ """Changes the player's volume"""
+
+ if ctx.author.voice is None:
+ return await ctx.send("You're not connected to a voice channel.")
+
+ if ctx.voice_client is None:
+ return await ctx.send("Not connected to a voice channel.")
+
+ ctx.voice_client.source.volume = volume / 100
+ await ctx.send(f"Changed volume to {volume}%")
+
+ @commands.command()
+ async def pause(self, ctx):
+ """Pauses audio"""
+
+ if ctx.author.voice is None:
+ return await ctx.send("You're not connected to a voice channel.")
+
+ voice = ctx.voice_client
+ if voice.is_playing():
+ voice.pause()
+ else:
+ await ctx.send("Currently no audio is playing.")
+
+ @commands.command()
+ async def resume(self, ctx):
+ """Resumes currently paused audio"""
+
+ if ctx.author.voice is None:
+ return await ctx.send("You're not connected to a voice channel.")
+
+ voice = ctx.voice_client
+ if voice.is_paused():
+ voice.resume()
+ else:
+ await ctx.send("The audio is not paused.")
+
+ @commands.command()
+ async def stop(self, ctx):
+ """Stops and disconnects the bot from voice"""
+
+ if ctx.author.voice is None:
+ return await ctx.send("You're not connected to a voice channel.")
+
+ voice = ctx.voice_client
+ if voice:
+ voice.stop()
+
+ @commands.command(aliases=['join'])
+ async def connect(self, ctx):
+ """Connects bot to currently connected voice channel"""
+
+ if ctx.author.voice is None:
+ return await ctx.send("You're not connected to a voice channel.")
+
+ channel = ctx.author.voice.channel
+ try:
+ await channel.connect()
+ except:
+ voice = ctx.voice_client
+ if voice.is_connected() and voice.channel != channel:
+ await voice.disconnect()
+ await channel.connect()
+
+def setup(bot):
+ bot.add_cog(AudioCog(bot))
\ No newline at end of file
diff --git a/cogs/info/calendarCog.py b/cogs/info/calendarCog.py
index 3f47268..f04489d 100644
--- a/cogs/info/calendarCog.py
+++ b/cogs/info/calendarCog.py
@@ -1,59 +1,56 @@
-import os
-import discord
-import DiscordUtils
-import datetime
-from dateutil.parser import parse
-import requests
-from discord.ext import commands
-
-class CalendarCog(commands.Cog):
-
- def __init__(self, client):
- self.client = client
-
- # Commands
- @commands.command()
- async def events(self, ctx):
- """Gets all upcoming events from Google Calendar"""
- # Check if environment variable exists
- if not os.environ.get("GOOGLE_CALENDAR_ENDPOINT"):
- return await ctx.send("No Google Calendar endpoint specified!")
-
- # Get upcoming events data from calendar
- current_time = datetime.datetime.utcnow()
- formatted_time = current_time.isoformat("T") + "Z"
- calendar = os.environ.get("GOOGLE_CALENDAR_ENDPOINT") + "&timeMin=" + formatted_time
- data = requests.get(calendar).json()
-
- # Check if there are any events
- if not data["items"]:
- return await ctx.send("There are no upcoming events!")
-
- # Get all upcoming events as a list
- list_of_events = data["items"]
- embeds = []
-
- for event in list_of_events:
- # Create data set
- title = event["summary"]
- start_time = event["start"]["dateTime"]
- description = "No description."
- if "description" in event:
- description = event["description"]
- formatted_start_time = datetime.datetime.strftime(parse(start_time), format="%B %d, %Y")
-
- # Create embed for single event and add to embeds list
- embed = discord.Embed(color=ctx.author.color, title=title, description=description)
- embed.add_field(name="Starts On", value=formatted_start_time, inline=True)
- embeds.append(embed)
-
- # Create paginator
- paginator = DiscordUtils.Pagination.CustomEmbedPaginator(ctx)
- paginator.add_reaction('⏮️', "first")
- paginator.add_reaction('⏪', "back")
- paginator.add_reaction('⏩', "next")
- paginator.add_reaction('⏭️', "last")
- await paginator.run(embeds)
-
-def setup(client):
- client.add_cog(CalendarCog(client))
\ No newline at end of file
+import os
+import discord
+import DiscordUtils
+import datetime
+from dateutil.parser import parse
+import requests
+from discord.ext import commands
+
+class CalendarCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command()
+ async def events(self, ctx):
+ """Gets all upcoming events from Google Calendar"""
+ # Check if environment variable exists
+ if not os.environ.get("GOOGLE_CALENDAR_ENDPOINT"):
+ return await ctx.send("No Google Calendar endpoint specified!")
+
+ # Get upcoming events data from calendar
+ current_time = datetime.datetime.utcnow()
+ formatted_time = current_time.isoformat("T") + "Z"
+ calendar = os.environ.get("GOOGLE_CALENDAR_ENDPOINT") + "&timeMin=" + formatted_time
+ data = requests.get(calendar).json()
+
+ # Check if there are any events
+ if not data["items"]:
+ return await ctx.send("There are no upcoming events!")
+
+ # Get all upcoming events as a list
+ list_of_events = data["items"]
+ embeds = []
+
+ for event in list_of_events:
+ # Create data set
+ title = event["summary"]
+ start_time = event["start"]["dateTime"]
+ description = event["description"] if "description" in event else "No description."
+ formatted_start_time = datetime.datetime.strftime(parse(start_time), format="%B %d, %Y")
+ # Create embed for single event and add to embeds list
+ embed = discord.Embed(color=ctx.author.color, title=title, description=description)
+ embed.add_field(name="Starts On", value=formatted_start_time, inline=True)
+ embeds.append(embed)
+
+ # Create paginator
+ paginator = DiscordUtils.Pagination.CustomEmbedPaginator(ctx)
+ paginator.add_reaction('⏮️', "first")
+ paginator.add_reaction('⏪', "back")
+ paginator.add_reaction('⏩', "next")
+ paginator.add_reaction('⏭️', "last")
+ await paginator.run(embeds)
+
+def setup(bot):
+ bot.add_cog(CalendarCog(bot))
\ No newline at end of file
diff --git a/cogs/info/infoCog.py b/cogs/info/infoCog.py
new file mode 100644
index 0000000..67e7bcf
--- /dev/null
+++ b/cogs/info/infoCog.py
@@ -0,0 +1,30 @@
+import discord
+from discord.ext import commands
+
+class InfoCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command(aliases=['botinfo'])
+ async def info(self, ctx):
+ embed = discord.Embed(
+ title=f"Rutgers Esports Bot <:RutgersEsports:608498339192766505>",
+ description=(
+ f'Rutgers Esports Bot is the official Discord bot\n'
+ 'for handling all internal Rutgers Esports operations.\n\n'
+ '🔗 Check out Rutgers Esports '
+ '[here](https://linktr.ee/RutgersEsports).\n'
+ '🤖 To add this bot to your server use '
+ '[this link](https://bit.ly/rutgers-esports-bot).\n'
+ "⭐ Star our repo on GitHub [here](https://github.com/rutgersesports/discord-bot)."
+ ),
+ color=0xC94949
+ )
+ embed.set_thumbnail(url=ctx.bot.user.avatar_url)
+
+ await ctx.send(embed=embed)
+
+def setup(bot):
+ bot.add_cog(InfoCog(bot))
\ No newline at end of file
diff --git a/cogs/info/pingCog.py b/cogs/info/pingCog.py
index fdf5c33..246220b 100644
--- a/cogs/info/pingCog.py
+++ b/cogs/info/pingCog.py
@@ -1,16 +1,16 @@
-import discord
-from discord.ext import commands
-
-class PingCog(commands.Cog):
-
- def __init__(self, client):
- self.client = client
-
- # Commands
- @commands.command(aliases=['latency'])
- async def ping(self, ctx):
- """Returns the bot client latency"""
- await ctx.send(f'Pong! {round(self.client.latency * 1000)}ms')
-
-def setup(client):
- client.add_cog(PingCog(client))
\ No newline at end of file
+import discord
+from discord.ext import commands
+
+class PingCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command(aliases=['latency'])
+ async def ping(self, ctx):
+ """Returns the bot bot latency"""
+ await ctx.send(f'Pong! {round(self.bot.latency * 1000)}ms')
+
+def setup(bot):
+ bot.add_cog(PingCog(bot))
\ No newline at end of file
diff --git a/cogs/info/serverCog.py b/cogs/info/serverCog.py
new file mode 100644
index 0000000..54e1ef1
--- /dev/null
+++ b/cogs/info/serverCog.py
@@ -0,0 +1,35 @@
+import discord
+from discord.ext import commands
+
+class ServerCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command(aliases=['server', 'sinfo'])
+ async def serverinfo(self, ctx):
+ """Get information about the current server"""
+ guild = ctx.guild
+
+ roles = str(len(guild.roles))
+ emojis = str(len(guild.emojis))
+ vchannels = str(len(guild.voice_channels))
+ tchannels = str(len(guild.text_channels))
+
+ embed = discord.Embed(title='Server info', description=guild.name, color=ctx.guild.get_member(ctx.bot.user.id).color)
+ embed.set_thumbnail(url=guild.icon_url)
+ embed.add_field(name='ID', value=guild.id, inline=True)
+ embed.add_field(name='Owner', value=guild.owner, inline=True)
+ embed.add_field(name='Members', value=guild.member_count, inline=True)
+ embed.add_field(name='Text channels', value=tchannels, inline=True)
+ embed.add_field(name='Voice channels', value=vchannels, inline=True)
+ embed.add_field(name='Created on', value=guild.created_at.strftime('%B %d, %Y'), inline=True)
+ embed.add_field(name='Region', value=guild.region, inline=True)
+ embed.add_field(name='Roles', value=roles, inline=True)
+ embed.add_field(name='Verification', value=guild.verification_level, inline=True)
+
+ await ctx.send(embed=embed)
+
+def setup(bot):
+ bot.add_cog(ServerCog(bot))
\ No newline at end of file
diff --git a/cogs/info/userCog.py b/cogs/info/userCog.py
new file mode 100644
index 0000000..4f1af81
--- /dev/null
+++ b/cogs/info/userCog.py
@@ -0,0 +1,44 @@
+import discord
+from discord.ext import commands
+
+class UserCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command(aliases=['whois'])
+ async def userinfo(self, ctx, member: discord.Member = None):
+ """Get information about a given user"""
+ # Gather data
+ member = ctx.author if not member else member
+ roles = [role for role in member.roles]
+ default_role = discord.utils.get(member.guild.roles, name='@everyone')
+ role_mentions = [f'{role.mention}' for role in sorted(member.roles, key=lambda x: x.position, reverse=True) if role != default_role]
+ all_perms = [x for x in dir(ctx.channel.permissions_for(member))]
+ permissions = []
+ for perm in all_perms:
+ perm_name = perm
+ if getattr(ctx.channel.permissions_for(member), perm_name) is True:
+ permissions.append(perm_name.title().replace("_", " ").replace("Tts", "TTS"))
+
+ # Create embed
+ embed = discord.Embed(description = member.mention, color = member.color, timestamp = ctx.message.created_at)
+
+ embed.set_author(name = member, icon_url = member.avatar_url)
+ embed.set_thumbnail(url = member.avatar_url)
+ embed.set_footer(text = member.id)
+
+ embed.add_field(name = 'Joined', value = member.joined_at.strftime('%a, %d %B %Y, %I:%M %p UTC'), inline=True)
+ embed.add_field(name = 'Registered', value = member.created_at.strftime('%a, %d %B %Y, %I:%M %p UTC'), inline=True)
+
+ embed.add_field(name=f'Roles [{len(roles)}]', value=", ".join(role_mentions)+f', {default_role}', inline=False)
+ embed.add_field(name=f'Permissions [{len(permissions)}]', value=", ".join(permissions), inline=False)
+
+
+ embed.add_field(name='Nickname', value=member.nick if hasattr(member, 'nick') else 'None', inline=True)
+
+ await ctx.send(embed = embed)
+
+def setup(bot):
+ bot.add_cog(UserCog(bot))
\ No newline at end of file
diff --git a/cogs/inventory/inventoryCog.py b/cogs/inventory/inventoryCog.py
index 239c0d7..d9f0dad 100644
--- a/cogs/inventory/inventoryCog.py
+++ b/cogs/inventory/inventoryCog.py
@@ -1,61 +1,61 @@
-import os
-import discord
-import DiscordUtils
-import asyncio
-from google.cloud import firestore
-from discord.ext import commands
-from dotenv import load_dotenv
-
-load_dotenv()
-
-class InventoryCog(commands.Cog):
-
- def __init__(self, client):
- self.client = client
- # Commands
-
- # Get a single item from inventory
- @commands.command(aliases=['inv'])
- @commands.is_owner()
- async def inventory(self, ctx, arg1):
- """Get a single item from the inventory database"""
-
- # Make single input embed
- def single_embed(item, id):
- embed = discord.Embed(title=item["item_name"], description=id)
- if item["item_type"] != "": embed.add_field(name="Item Type", value=item["item_type"], inline=True)
- if item["color"] != "": embed.add_field(name="Color", value=item["color"], inline=True)
- if item["brand"] != "": embed.add_field(name="Brand", value=item["brand"], inline=True)
- if item["model"] != "": embed.add_field(name="Model", value=item["model"], inline=True)
- if item["size"] != "": embed.add_field(name="Size", value=item["size"], inline=True)
- if item["quantity"] != "": embed.add_field(name="Quantity", value=item["quantity"], inline=True)
- if item["box"] != "": embed.add_field(name="Box", value=item["box"], inline=True)
- return embed
-
- # Connect to Firestore DB inventory collection and get the item arg1 as doc
- if "INVENTORY_PROJECT_ID" in os.environ and "GOOGLE_APPLICATION_CREDENTIALS" in os.environ:
- try:
- db = firestore.AsyncClient(project=os.environ.get("INVENTORY_PROJECT_ID"))
- inventory_ref = db.collection("inventory").document(arg1)
- doc = await inventory_ref.get()
-
- if doc.exists:
- await ctx.send(embed=single_embed(doc.to_dict(), doc.id))
- else:
- await ctx.send("That ID doesn't exist!")
- except:
- await ctx.send("The DB connection is not working.")
- elif "INVENTORY_PROJECT_ID" not in os.environ and "GOOGLE_APPLICATION_CREDENTIALS" in os.environ:
- await ctx.send("Inventory Project ID not found!")
- elif "GOOGLE_APPLICATION_CREDENTIALS" not in os.environ:
- await ctx.send("GAPP Credentials not found!")
-
- @inventory.error
- async def inventory_error(self, ctx, error):
- if isinstance(error, commands.MissingRequiredArgument):
- await ctx.send("Missing required argument! (e.g. d!inventory __10000__)")
- if isinstance(error, commands.NotOwner):
- await ctx.send("You do not have permissions to run this command.")
-
-def setup(client):
- client.add_cog(InventoryCog(client))
\ No newline at end of file
+import os
+import discord
+import DiscordUtils
+import asyncio
+from google.cloud import firestore
+from discord.ext import commands
+from dotenv import load_dotenv
+
+load_dotenv()
+
+class InventoryCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ # Get a single item from inventory
+ @commands.command(aliases=['inv'])
+ @commands.is_owner()
+ async def inventory(self, ctx, arg1):
+ """Get a single item from the inventory database"""
+
+ # Make single input embed
+ def single_embed(item, id):
+ embed = discord.Embed(title=item["item_name"], description=id)
+ if item["item_type"] != "": embed.add_field(name="Item Type", value=item["item_type"], inline=True)
+ if item["color"] != "": embed.add_field(name="Color", value=item["color"], inline=True)
+ if item["brand"] != "": embed.add_field(name="Brand", value=item["brand"], inline=True)
+ if item["model"] != "": embed.add_field(name="Model", value=item["model"], inline=True)
+ if item["size"] != "": embed.add_field(name="Size", value=item["size"], inline=True)
+ if item["quantity"] != "": embed.add_field(name="Quantity", value=item["quantity"], inline=True)
+ if item["box"] != "": embed.add_field(name="Box", value=item["box"], inline=True)
+ return embed
+
+ # Connect to Firestore DB inventory collection and get the item arg1 as doc
+ if "INVENTORY_PROJECT_ID" in os.environ and "GOOGLE_APPLICATION_CREDENTIALS" in os.environ:
+ try:
+ db = firestore.AsyncClient(project=os.environ.get("INVENTORY_PROJECT_ID"))
+ inventory_ref = db.collection("inventory").document(arg1)
+ doc = await inventory_ref.get()
+
+ if doc.exists:
+ await ctx.send(embed=single_embed(doc.to_dict(), doc.id))
+ else:
+ await ctx.send("That ID doesn't exist!")
+ except:
+ await ctx.send("The DB connection is not working.")
+ elif "INVENTORY_PROJECT_ID" not in os.environ and "GOOGLE_APPLICATION_CREDENTIALS" in os.environ:
+ await ctx.send("Inventory Project ID not found!")
+ elif "GOOGLE_APPLICATION_CREDENTIALS" not in os.environ:
+ await ctx.send("GAPP Credentials not found!")
+
+ @inventory.error
+ async def inventory_error(self, ctx, error):
+ if isinstance(error, commands.MissingRequiredArgument):
+ await ctx.send("Missing required argument! (e.g. d!inventory __10000__)")
+ if isinstance(error, commands.NotOwner):
+ await ctx.send("You do not have permissions to run this command.")
+
+def setup(bot):
+ bot.add_cog(InventoryCog(bot))
\ No newline at end of file
diff --git a/cogs/moderation/embedCog.py b/cogs/moderation/embedCog.py
new file mode 100644
index 0000000..dfd370a
--- /dev/null
+++ b/cogs/moderation/embedCog.py
@@ -0,0 +1,64 @@
+import discord
+from discord.ext import commands
+
+class EmbedCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command()
+ async def embed(self, ctx, *, data):
+ """Produces a customizable embed"""
+ # Split arguments into a list
+ data = data.split("%%")
+
+ # Set default channel to current channel where command is used
+ channel = ctx.channel
+
+ # Remove [''] surrounding possible channel argument input (slice value to just channel ID)
+ possible_channel = data[0].rstrip()[2:-1]
+
+ # Set channel if channel argument is given
+ try:
+ channel = await commands.TextChannelConverter().convert(ctx, possible_channel)
+ data.pop(0)
+ except Exception as e:
+ pass
+
+ # Check if user has permission to send in given channel
+ if not ctx.guild.get_member(ctx.author.id).permissions_in(channel).send_messages:
+ return await ctx.send(f"You don't have permission to send messages to {channel.mention}!")
+
+
+ # Add possible empty list values to deter IndexError for embed
+ data += [""] * (3-len(data))
+
+ # Create and send embed
+ embed = discord.Embed(
+ title=f"{data[0]}",
+ description=f"{data[1]}",
+ color=ctx.guild.get_member(ctx.bot.user.id).color
+ )
+ embed.set_footer(
+ text=f"{data[2]}"
+ )
+ await channel.send(embed=embed)
+
+ @embed.error
+ async def embed_error(self, ctx, error):
+ await ctx.send(
+ "Type the command in the following format:```.embed #channel_name %% title %% description %% footer```"
+ )
+ embed = discord.Embed(
+ title="Title",
+ description="Description",
+ color=ctx.guild.get_member(ctx.bot.user.id).color
+ )
+ embed.set_footer(
+ text="Footer"
+ )
+ await ctx.send(embed=embed)
+
+def setup(bot):
+ bot.add_cog(EmbedCog(bot))
\ No newline at end of file
diff --git a/cogs/moderation/moderationCog.py b/cogs/moderation/moderationCog.py
index a769e5c..2eed918 100644
--- a/cogs/moderation/moderationCog.py
+++ b/cogs/moderation/moderationCog.py
@@ -1,31 +1,31 @@
-import discord
-from discord.ext import commands
-
-class ModerationCog(commands.Cog):
-
- def __init__(self, client):
- self.client = client
-
- # Commands
- @commands.command()
- @commands.has_permissions(kick_members = True)
- async def kick(self, ctx, member : commands.MemberConverter, *, reason="No reason provided."):
- """Kick a user from the server"""
- try:
- await member.kick(reason=reason)
- await ctx.send(member.mention + " has been kicked.")
- except:
- await ctx.send(f"Unable to kick {member.mention}.\nIs {member.mention} at the same role level or higher than {self.client.user.name}?")
-
- @commands.command()
- @commands.has_permissions(ban_members = True)
- async def ban(self, ctx, member : commands.MemberConverter, *, reason="No reason provided."):
- """Ban a user from the server"""
- try:
- await member.ban(reason=reason)
- await ctx.send(member.mention + " has been banned.")
- except:
- await ctx.send(f"Unable to ban {member.mention}.\nIs {member.mention} at the same role level or higher than {self.client.user.name}?")
-
-def setup(client):
- client.add_cog(ModerationCog(client))
\ No newline at end of file
+import discord
+from discord.ext import commands
+
+class ModerationCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command()
+ @commands.has_permissions(kick_members = True)
+ async def kick(self, ctx, member : commands.MemberConverter, *, reason="No reason provided."):
+ """Kick a user from the server"""
+ try:
+ await member.kick(reason=reason)
+ await ctx.send(member.mention + " has been kicked.")
+ except:
+ await ctx.send(f"Unable to kick {member.mention}.\nIs {member.mention} at the same role level or higher than {self.bot.user.name}?")
+
+ @commands.command()
+ @commands.has_permissions(ban_members = True)
+ async def ban(self, ctx, member : commands.MemberConverter, *, reason="No reason provided."):
+ """Ban a user from the server"""
+ try:
+ await member.ban(reason=reason)
+ await ctx.send(member.mention + " has been banned.")
+ except:
+ await ctx.send(f"Unable to ban {member.mention}.\nIs {member.mention} at the same role level or higher than {self.bot.user.name}?")
+
+def setup(bot):
+ bot.add_cog(ModerationCog(bot))
\ No newline at end of file
diff --git a/cogs/moderation/pruneCog.py b/cogs/moderation/pruneCog.py
new file mode 100644
index 0000000..5a7cc8c
--- /dev/null
+++ b/cogs/moderation/pruneCog.py
@@ -0,0 +1,18 @@
+import discord
+from discord.ext import commands
+
+class PruneCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command(aliases=['clear', 'prune'])
+ @commands.has_permissions(manage_messages=True)
+ async def purge(self, ctx, amount=10):
+ """Removes a number of messages (10 by default)"""
+ await ctx.channel.purge(limit=amount+1)
+ await ctx.send(f'`{amount}` messages deleted.', delete_after=3)
+
+def setup(bot):
+ bot.add_cog(PruneCog(bot))
\ No newline at end of file
diff --git a/cogs/owner/loaderCog.py b/cogs/owner/loaderCog.py
new file mode 100644
index 0000000..2f5da8a
--- /dev/null
+++ b/cogs/owner/loaderCog.py
@@ -0,0 +1,23 @@
+import discord
+from discord.ext import commands
+
+class LoaderCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.is_owner()
+ @commands.command()
+ async def load(self, ctx, extension):
+ """Loads a cog"""
+ self.bot.load_extension(f'cogs.{extension}')
+
+ @commands.is_owner()
+ @commands.command()
+ async def unload(self, ctx, extension):
+ """Unloads a cog"""
+ self.bot.unload_extension(f'cogs.{extension}')
+
+def setup(bot):
+ bot.add_cog(LoaderCog(bot))
\ No newline at end of file
diff --git a/cogs/owner/sayCog.py b/cogs/owner/sayCog.py
new file mode 100644
index 0000000..f5db55e
--- /dev/null
+++ b/cogs/owner/sayCog.py
@@ -0,0 +1,18 @@
+import discord
+from discord.ext import commands
+
+class SayCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command(aliases=['speak'])
+ @commands.is_owner()
+ async def say(self, ctx, *, content):
+ """Lets the bot say given arguments"""
+ await ctx.message.delete()
+ await ctx.send(content)
+
+def setup(bot):
+ bot.add_cog(SayCog(bot))
\ No newline at end of file
diff --git a/cogs/owner/statusCog.py b/cogs/owner/statusCog.py
new file mode 100644
index 0000000..7f4c6e3
--- /dev/null
+++ b/cogs/owner/statusCog.py
@@ -0,0 +1,29 @@
+import discord
+from discord.ext import commands
+
+class StatusCog(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ # Commands
+ @commands.command(aliases=['botstatus', 'stat'])
+ @commands.is_owner()
+ async def status(self, ctx):
+ """Gives bot statistics"""
+ # Total members of all servers bot is in
+ total_guild_users = 0
+ for guild in ctx.bot.guilds:
+ total_guild_users += guild.member_count
+
+ embed = discord.Embed(
+ title=f"Bot Status",
+ description=(f'`{ctx.bot.user.name}` is online.'
+ f'\nBot is in `{len(ctx.bot.guilds)}` guilds.'
+ f'\nGuilds have `{total_guild_users}` members.'),
+ color=ctx.guild.get_member(ctx.bot.user.id).color
+ )
+ await ctx.send(embed=embed)
+
+def setup(bot):
+ bot.add_cog(StatusCog(bot))
\ No newline at end of file
diff --git a/images/Bot-Example.png b/images/Bot-Example.png
new file mode 100644
index 0000000..950c533
Binary files /dev/null and b/images/Bot-Example.png differ
diff --git a/images/GitHub-Banner.png b/images/GitHub-Banner.png
new file mode 100644
index 0000000..b60ba52
Binary files /dev/null and b/images/GitHub-Banner.png differ
diff --git a/images/icons/discord.svg b/images/icons/discord.svg
new file mode 100644
index 0000000..ca65400
--- /dev/null
+++ b/images/icons/discord.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/icons/facebook-alt.svg b/images/icons/facebook-alt.svg
new file mode 100644
index 0000000..dd748b1
--- /dev/null
+++ b/images/icons/facebook-alt.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/images/icons/facebook.svg b/images/icons/facebook.svg
new file mode 100644
index 0000000..b328631
--- /dev/null
+++ b/images/icons/facebook.svg
@@ -0,0 +1,39 @@
+
+
+
diff --git a/images/icons/github.svg b/images/icons/github.svg
new file mode 100644
index 0000000..3fac1ed
--- /dev/null
+++ b/images/icons/github.svg
@@ -0,0 +1,44 @@
+
+
+
diff --git a/images/icons/instagram.svg b/images/icons/instagram.svg
new file mode 100644
index 0000000..9371bdc
--- /dev/null
+++ b/images/icons/instagram.svg
@@ -0,0 +1,57 @@
+
+
+
diff --git a/images/icons/link.svg b/images/icons/link.svg
new file mode 100644
index 0000000..d82cb90
--- /dev/null
+++ b/images/icons/link.svg
@@ -0,0 +1,25 @@
+
+
+
diff --git a/images/icons/linked-in-alt.svg b/images/icons/linked-in-alt.svg
new file mode 100644
index 0000000..1f9463a
--- /dev/null
+++ b/images/icons/linked-in-alt.svg
@@ -0,0 +1,42 @@
+
+
+
diff --git a/images/icons/linked-in.svg b/images/icons/linked-in.svg
new file mode 100644
index 0000000..4e7b515
--- /dev/null
+++ b/images/icons/linked-in.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/images/icons/mail.svg b/images/icons/mail.svg
new file mode 100644
index 0000000..2b5177d
--- /dev/null
+++ b/images/icons/mail.svg
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/images/icons/reddit.svg b/images/icons/reddit.svg
new file mode 100644
index 0000000..e13d583
--- /dev/null
+++ b/images/icons/reddit.svg
@@ -0,0 +1,54 @@
+
+
+
diff --git a/images/icons/twitch.svg b/images/icons/twitch.svg
new file mode 100644
index 0000000..3120fea
--- /dev/null
+++ b/images/icons/twitch.svg
@@ -0,0 +1,21 @@
+
+
+
diff --git a/images/icons/twitter-alt.svg b/images/icons/twitter-alt.svg
new file mode 100644
index 0000000..165cd7c
--- /dev/null
+++ b/images/icons/twitter-alt.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/images/icons/twitter.svg b/images/icons/twitter.svg
new file mode 100644
index 0000000..1303a71
--- /dev/null
+++ b/images/icons/twitter.svg
@@ -0,0 +1,43 @@
+
+
+
diff --git a/images/icons/youtube.svg b/images/icons/youtube.svg
new file mode 100644
index 0000000..62005ce
--- /dev/null
+++ b/images/icons/youtube.svg
@@ -0,0 +1,44 @@
+
+
+