First official build of bot!

Co-authored-by: iakrules <64628083+iakrules@noreply.github.com>
This commit is contained in:
rzmk 2021-08-01 14:24:02 -04:00
parent 953c89ead0
commit 2b8e2345f9
32 changed files with 1164 additions and 383 deletions

6
.gitignore vendored
View file

@ -1,4 +1,4 @@
.env
env
__pycache__
.env
env
__pycache__
song.mp3

View file

@ -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).
<p align="center">
<img src="./images/GitHub-Banner.png">
</p>
<div style="display: flex; justify-content: center;" align="center">
<a href="https://discord.gg/RutgersEsports"><img src="./images/icons/discord.svg" align="center" height=40 width=40 style="margin: 0px 10px 0px 0px;"></img></a>
<a href="https://twitter.com/RutgersEsports"><img src="./images/icons/twitter.svg" align="center" height=40 width=40 style="margin: 0px 10px 0px 0px;"></img></a>
<a href="https://instagram.com/rutgersesports"><img src="./images/icons/instagram.svg" align="center" height=40 width=40 style="margin: 0px 10px 0px 0px;"></img></a>
<a href="https://twitch.tv/rutgersesports"><img src="./images/icons/twitch.svg" align="center" height=40 width=40 style="margin: 0px 10px 0px 0px;"></img></a>
<a href="https://www.linkedin.com/company/rutgers-esports"><img src="./images/icons/linked-in-alt.svg" align="center" height=40 width=40 style="margin: 0px 10px 0px 0px;"></img></a>
<a href="https://facebook.com/rutgersesports"><img src="./images/icons/facebook.svg" align="center" height=40 width=40 style="margin: 0px 10px 0px 0px;"></img></a>
<a href="https://youtube.com/rutgersesports"><img src="./images/icons/youtube.svg" align="center" height=40 width=40 style="margin: 0px 5px 0px 0px;"></img></a>
<a href="mailto:rutgersesports@gmail.com"><img src="./images/icons/mail.svg" align="center" height=40 width=40 style="margin: 0px 5px 0px 0px;"></img></a>
</div>
---
# 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
[![Bot Example](./images/Bot-Example.png)](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!
<a href="https://github.com/rutgersesports/discord-bot/graphs/contributors">
<img src="https://contrib.rocks/image?repo=rutgersesports/discord-bot" />
</a>

82
bot.py
View file

@ -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)
# 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)

View file

@ -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))
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))

View file

@ -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))
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))

30
cogs/info/infoCog.py Normal file
View file

@ -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))

View file

@ -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))
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))

35
cogs/info/serverCog.py Normal file
View file

@ -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))

44
cogs/info/userCog.py Normal file
View file

@ -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))

View file

@ -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))
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))

View file

@ -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))

View file

@ -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))
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))

View file

@ -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))

23
cogs/owner/loaderCog.py Normal file
View file

@ -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))

18
cogs/owner/sayCog.py Normal file
View file

@ -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))

29
cogs/owner/statusCog.py Normal file
View file

@ -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))

BIN
images/Bot-Example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
images/GitHub-Banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

1
images/icons/discord.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><defs><style>.cls-1{fill:#5865f2;}</style></defs><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White"><path class="cls-1" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 988 B

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#1976D2;" d="M384,176h-96v-64c0-17.664,14.336-32,32-32h32V0h-64l0,0c-53.024,0-96,42.976-96,96v80h-64v80h64v256
h96V256h64L384,176z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 685 B

39
images/icons/facebook.svg Normal file
View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#1976D2;" d="M448,0H64C28.704,0,0,28.704,0,64v384c0,35.296,28.704,64,64,64h384c35.296,0,64-28.704,64-64V64
C512,28.704,483.296,0,448,0z"/>
<path style="fill:#FAFAFA;" d="M432,256h-80v-64c0-17.664,14.336-16,32-16h32V96h-64l0,0c-53.024,0-96,42.976-96,96v64h-64v80h64
v176h96V336h48L432,256z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 846 B

44
images/icons/github.svg Normal file
View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#5C6BC0;" d="M255.968,5.329C114.624,5.329,0,120.401,0,262.353c0,113.536,73.344,209.856,175.104,243.872
c12.8,2.368,17.472-5.568,17.472-12.384c0-6.112-0.224-22.272-0.352-43.712c-71.2,15.52-86.24-34.464-86.24-34.464
c-11.616-29.696-28.416-37.6-28.416-37.6c-23.264-15.936,1.728-15.616,1.728-15.616c25.696,1.824,39.2,26.496,39.2,26.496
c22.848,39.264,59.936,27.936,74.528,21.344c2.304-16.608,8.928-27.936,16.256-34.368c-56.832-6.496-116.608-28.544-116.608-127.008
c0-28.064,9.984-51.008,26.368-68.992c-2.656-6.496-11.424-32.64,2.496-68c0,0,21.504-6.912,70.4,26.336
c20.416-5.696,42.304-8.544,64.096-8.64c21.728,0.128,43.648,2.944,64.096,8.672c48.864-33.248,70.336-26.336,70.336-26.336
c13.952,35.392,5.184,61.504,2.56,68c16.416,17.984,26.304,40.928,26.304,68.992c0,98.72-59.84,120.448-116.864,126.816
c9.184,7.936,17.376,23.616,17.376,47.584c0,34.368-0.32,62.08-0.32,70.496c0,6.88,4.608,14.88,17.6,12.352
C438.72,472.145,512,375.857,512,262.353C512,120.401,397.376,5.329,255.968,5.329z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-46.0041" y1="634.1208" x2="-32.9334" y2="647.1917" gradientTransform="matrix(32 0 0 -32 1519 20757)">
<stop offset="0" style="stop-color:#FFC107"/>
<stop offset="0.507" style="stop-color:#F44336"/>
<stop offset="0.99" style="stop-color:#9C27B0"/>
</linearGradient>
<path style="fill:url(#SVGID_1_);" d="M352,0H160C71.648,0,0,71.648,0,160v192c0,88.352,71.648,160,160,160h192
c88.352,0,160-71.648,160-160V160C512,71.648,440.352,0,352,0z M464,352c0,61.76-50.24,112-112,112H160c-61.76,0-112-50.24-112-112
V160C48,98.24,98.24,48,160,48h192c61.76,0,112,50.24,112,112V352z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-42.2971" y1="637.8279" x2="-36.6404" y2="643.4846" gradientTransform="matrix(32 0 0 -32 1519 20757)">
<stop offset="0" style="stop-color:#FFC107"/>
<stop offset="0.507" style="stop-color:#F44336"/>
<stop offset="0.99" style="stop-color:#9C27B0"/>
</linearGradient>
<path style="fill:url(#SVGID_2_);" d="M256,128c-70.688,0-128,57.312-128,128s57.312,128,128,128s128-57.312,128-128
S326.688,128,256,128z M256,336c-44.096,0-80-35.904-80-80c0-44.128,35.904-80,80-80s80,35.872,80,80
C336,300.096,300.096,336,256,336z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-35.5456" y1="644.5793" x2="-34.7919" y2="645.3331" gradientTransform="matrix(32 0 0 -32 1519 20757)">
<stop offset="0" style="stop-color:#FFC107"/>
<stop offset="0.507" style="stop-color:#F44336"/>
<stop offset="0.99" style="stop-color:#9C27B0"/>
</linearGradient>
<circle style="fill:url(#SVGID_3_);" cx="393.6" cy="118.4" r="17.056"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

25
images/icons/link.svg Normal file
View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<g>
<path fill="#6E83B7" d="M329.5,298.515c-29.705,0-59.41-11.307-82.024-33.921l0,0c-45.228-45.229-45.228-118.821,0-164.049
l56.569-56.569c45.229-45.229,118.82-45.229,164.048,0c45.229,45.229,45.229,118.821,0,164.049l-56.569,56.569
C388.91,287.208,359.205,298.515,329.5,298.515z M284.245,227.824c24.955,24.954,65.557,24.953,90.51,0l56.569-56.569
c24.954-24.954,24.953-65.556,0-90.51c-24.954-24.953-65.557-24.953-90.51,0l-56.569,56.569
C259.292,162.268,259.292,202.87,284.245,227.824l-18.385,18.384L284.245,227.824z"/>
</g>
<g>
<path fill="#6E83B7" d="M126,502.015c-29.705,0-59.41-11.307-82.024-33.921c-45.228-45.229-45.228-118.821,0-164.049
l56.569-56.569c45.229-45.229,118.82-45.228,164.048,0h0c45.228,45.229,45.228,118.821,0,164.049l-56.569,56.569
C185.41,490.708,155.705,502.015,126,502.015z M182.568,265.53c-16.389,0-32.778,6.238-45.254,18.715l-56.569,56.569
c-24.954,24.954-24.954,65.556,0,90.509c24.954,24.954,65.557,24.954,90.51,0l56.569-56.569c24.954-24.954,24.953-65.556,0-90.51
C215.347,271.769,198.958,265.53,182.568,265.53z"/>
</g>
<g>
<rect x="142.863" y="225" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -106.0387 256)" fill="#466089" width="226.274" height="62"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<rect y="160" style="fill:#1976D2;" width="114.496" height="352"/>
<path style="fill:#1976D2;" d="M426.368,164.128c-1.216-0.384-2.368-0.8-3.648-1.152c-1.536-0.352-3.072-0.64-4.64-0.896
c-6.08-1.216-12.736-2.08-20.544-2.08l0,0l0,0c-66.752,0-109.088,48.544-123.04,67.296V160H160v352h114.496V320
c0,0,86.528-120.512,123.04-32c0,79.008,0,224,0,224H512V274.464C512,221.28,475.552,176.96,426.368,164.128z"/>
<circle style="fill:#1976D2;" cx="56" cy="56" r="56"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1,010 B

View file

@ -0,0 +1,3 @@
<svg viewBox="0 0 128 128">
<g id="original"><path fill="#0076b2" d="M116,3H12a8.91,8.91,0,0,0-9,8.8V116.22A8.91,8.91,0,0,0,12,125H116a8.93,8.93,0,0,0,9-8.81V11.77A8.93,8.93,0,0,0,116,3Z"></path><path fill="#fff" d="M21.06,48.73H39.17V107H21.06Zm9.06-29a10.5,10.5,0,1,1-10.5,10.49,10.5,10.5,0,0,1,10.5-10.49"></path><path fill="#fff" d="M50.53,48.73H67.89v8h.24c2.42-4.58,8.32-9.41,17.13-9.41C103.6,47.28,107,59.35,107,75v32H88.89V78.65c0-6.75-.12-15.44-9.41-15.44s-10.87,7.36-10.87,15V107H50.53Z"></path></g>
</svg>

After

Width:  |  Height:  |  Size: 516 B

25
images/icons/mail.svg Normal file
View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="6.82666in" height="6.82666in" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 6.82666 6.82666"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
<![CDATA[
.fil0 {fill:none}
.fil2 {fill:#039BE5}
.fil1 {fill:#29B6F6}
]]>
</style>
</defs>
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<g id="_488781256">
<rect id="_488782264" class="fil0" width="6.82666" height="6.82666"/>
<rect id="_488781280" class="fil0" x="0.853331" y="0.853331" width="5.12" height="5.12"/>
</g>
<polygon class="fil1" points="5.98167,1.97552 0.853335,1.98925 3.39349,3.55149 "/>
<polygon class="fil2" points="0.853335,1.98925 0.853335,4.84717 5.97333,4.84717 5.97333,1.97949 3.46123,3.4484 3.40515,3.48119 3.34906,3.4484 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

54
images/icons/reddit.svg Normal file
View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 514.24 514.24" style="enable-background:new 0 0 514.24 514.24;" xml:space="preserve">
<g>
<polygon style="fill:#FAFAFA;" points="192,289.121 176,289.089 176,289.121 "/>
<polygon style="fill:#FAFAFA;" points="336,289.121 352,289.121 336,289.089 "/>
</g>
<path style="fill:#FF9800;" d="M514.24,241.121c0-35.296-28.704-64-64-64c-14.112,0-27.52,4.608-38.464,12.96
c-35.712-24.8-82.496-39.584-132.256-43.328l26.848-62.976l78.112,18.24c2.496,24.16,22.72,43.104,47.52,43.104
c26.464,0,48-21.536,48-48s-21.536-48-48-48c-16.768,0-31.52,8.672-40.096,21.76l-91.296-21.344
c-7.744-1.76-15.328,2.176-18.4,9.312l-37.12,87.04c-53.728,1.856-104.928,16.992-143.584,43.36
C90.976,181.441,77.92,177.121,64,177.121c-35.296,0-64,28.704-64,64c0,23.392,12.768,44.544,32.352,55.392
c-0.256,2.816-0.352,5.696-0.352,8.608c0,88.224,100.48,160,224,160c123.488,0,224-71.776,224-160c0-2.496-0.096-4.96-0.256-7.424
C500.608,287.073,514.24,265.409,514.24,241.121z"/>
<g>
<circle style="fill:#FAFAFA;" cx="432" cy="97.121" r="16"/>
<circle style="fill:#FAFAFA;" cx="176" cy="289.121" r="32"/>
<path style="fill:#FAFAFA;" d="M329.888,395.265c-22.08,15.968-48,23.968-73.888,23.968s-51.808-8-73.888-23.968
c-7.168-5.184-8.768-15.2-3.584-22.336s15.2-8.736,22.336-3.584c32.992,23.84,77.28,23.904,110.272,0
c7.136-5.152,17.12-3.616,22.336,3.584C338.656,380.097,337.024,390.081,329.888,395.265z"/>
<circle style="fill:#FAFAFA;" cx="336" cy="289.121" r="32"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

21
images/icons/twitch.svg Normal file
View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2400 2800" style="enable-background:new 0 0 2400 2800;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#9146FF;}
</style>
<title>Asset 2</title>
<g>
<polygon class="st0" points="2200,1300 1800,1700 1400,1700 1050,2050 1050,1700 600,1700 600,200 2200,200 "/>
<g>
<g id="Layer_1-2">
<path class="st1" d="M500,0L0,500v1800h600v500l500-500h400l900-900V0H500z M2200,1300l-400,400h-400l-350,350v-350H600V200h1600
V1300z"/>
<rect x="1700" y="550" class="st1" width="200" height="600"/>
<rect x="1150" y="550" class="st1" width="200" height="600"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 890 B

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#03A9F4;" d="M384,384H256c-35.296,0-64-28.704-64-64v-32h192c35.328,0,64-28.672,64-64s-28.672-64-64-64H192V64
c0-35.328-28.672-64-64-64S64,28.672,64,64v256c0,105.888,86.112,192,192,192h128c35.328,0,64-28.672,64-64S419.328,384,384,384z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 788 B

43
images/icons/twitter.svg Normal file
View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#03A9F4;" d="M512,97.248c-19.04,8.352-39.328,13.888-60.48,16.576c21.76-12.992,38.368-33.408,46.176-58.016
c-20.288,12.096-42.688,20.64-66.56,25.408C411.872,60.704,384.416,48,354.464,48c-58.112,0-104.896,47.168-104.896,104.992
c0,8.32,0.704,16.32,2.432,23.936c-87.264-4.256-164.48-46.08-216.352-109.792c-9.056,15.712-14.368,33.696-14.368,53.056
c0,36.352,18.72,68.576,46.624,87.232c-16.864-0.32-33.408-5.216-47.424-12.928c0,0.32,0,0.736,0,1.152
c0,51.008,36.384,93.376,84.096,103.136c-8.544,2.336-17.856,3.456-27.52,3.456c-6.72,0-13.504-0.384-19.872-1.792
c13.6,41.568,52.192,72.128,98.08,73.12c-35.712,27.936-81.056,44.768-130.144,44.768c-8.608,0-16.864-0.384-25.12-1.44
C46.496,446.88,101.6,464,161.024,464c193.152,0,298.752-160,298.752-298.688c0-4.64-0.16-9.12-0.384-13.568
C480.224,136.96,497.728,118.496,512,97.248z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

44
images/icons/youtube.svg Normal file
View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<path style="fill:#F44336;" d="M490.24,113.92c-13.888-24.704-28.96-29.248-59.648-30.976C399.936,80.864,322.848,80,256.064,80
c-66.912,0-144.032,0.864-174.656,2.912c-30.624,1.76-45.728,6.272-59.744,31.008C7.36,138.592,0,181.088,0,255.904
C0,255.968,0,256,0,256c0,0.064,0,0.096,0,0.096v0.064c0,74.496,7.36,117.312,21.664,141.728
c14.016,24.704,29.088,29.184,59.712,31.264C112.032,430.944,189.152,432,256.064,432c66.784,0,143.872-1.056,174.56-2.816
c30.688-2.08,45.76-6.56,59.648-31.264C504.704,373.504,512,330.688,512,256.192c0,0,0-0.096,0-0.16c0,0,0-0.064,0-0.096
C512,181.088,504.704,138.592,490.24,113.92z"/>
<polygon style="fill:#FAFAFA;" points="192,352 192,160 352,256 "/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB