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
env env
__pycache__ __pycache__
song.mp3 song.mp3

View file

@ -1,4 +1,68 @@
# Duckster <p align="center">
A fun and all-purpose bot made in Python. <img src="./images/GitHub-Banner.png">
</p>
Founded by [@iakrules](https://github.com/iakrules) and [@rzmk](https://github.com/rzmk).
<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 all required packages and variables
import os import os
import discord import discord
from discord.ext import commands from discord.ext import commands
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
TOKEN = os.environ.get("TOKEN") intents = discord.Intents().all()
client = commands.Bot(command_prefix='d!')
TOKEN = os.environ.get("TOKEN")
# Bot initialized bot = commands.Bot(command_prefix='.', intents=intents)
@client.event
async def on_ready(): # Bot initialized
print(f'{client.user.name} is ready.') @bot.event
await client.change_presence(activity=discord.Streaming(name="duck pictures.", url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")) async def on_ready():
print(f'{bot.user.name} is ready.')
@commands.is_owner() await bot.change_presence(activity=discord.Streaming(name="Use .help to learn more!", url="https://linktr.ee/RutgersEsports"))
@client.command()
async def load(ctx, extension): class CustomHelpCommand(commands.MinimalHelpCommand):
"""Loads a cog""" async def send_pages(self):
client.load_extension(f'cogs.{extension}') destination = self.get_destination()
for page in self.paginator.pages:
@commands.is_owner() embed = discord.Embed(description=page, color=0xC94949)
@client.command() await destination.send(embed=embed)
async def unload(ctx, extension):
"""Unloads a cog""" bot.help_command = CustomHelpCommand()
client.unload_extension(f'cogs.{extension}')
# Cogs
# Cogs for group in os.listdir('./cogs'):
for filename in os.listdir('./cogs/info'): for cog in os.listdir(f'./cogs/{group}'):
if filename.endswith('.py'): if cog.endswith('.py'):
client.load_extension(f'cogs.info.{filename[:-3]}') bot.load_extension(f'cogs.{group}.{cog[:-3]}')
for filename in os.listdir('./cogs/moderation'): bot.run(TOKEN)
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)

View file

@ -1,162 +1,161 @@
import asyncio import asyncio
import discord import discord
from discord.ext import commands from discord.ext import commands
import youtube_dl import youtube_dl
import os import os
ffmpeg_options = { ffmpeg_options = {
'options': '-vn' 'options': '-vn'
} }
ytdl_format_options = { ytdl_format_options = {
'format': 'bestaudio/best', 'format': 'bestaudio/best',
'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s', 'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s',
'restrictfilenames': True, 'restrictfilenames': True,
'noplaylist': True, 'noplaylist': True,
'nocheckcertificate': True, 'nocheckcertificate': True,
'ignoreerrors': False, 'ignoreerrors': False,
'logtostderr': False, 'logtostderr': False,
'quiet': True, 'quiet': True,
'no_warnings': True, 'no_warnings': True,
'default_search': 'auto', 'default_search': 'auto',
'source_address': '0.0.0.0' # bind to ipv4 since ipv6 addresses cause issues sometimes 'source_address': '0.0.0.0' # Bind to ipv4 since ipv6 addresses cause issues sometimes
} }
ytdl = youtube_dl.YoutubeDL(ytdl_format_options) ytdl = youtube_dl.YoutubeDL(ytdl_format_options)
class YTDLSource(discord.PCMVolumeTransformer): class YTDLSource(discord.PCMVolumeTransformer):
def __init__(self, source, *, data, volume=0.5): def __init__(self, source, *, data, volume=0.5):
super().__init__(source, volume) super().__init__(source, volume)
self.data = data self.data = data
self.title = data.get('title') self.title = data.get('title')
self.url = data.get('url') self.url = data.get('url')
@classmethod @classmethod
async def from_url(cls, url, *, loop=None, stream=False): async def from_url(cls, url, *, loop=None, stream=False):
loop = loop or asyncio.get_event_loop() loop = loop or asyncio.get_event_loop()
data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream)) data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))
if 'entries' in data: if 'entries' in data:
# take first item from a playlist # Take first item from a playlist
data = data['entries'][0] data = data['entries'][0]
filename = data['url'] if stream else ytdl.prepare_filename(data) filename = data['url'] if stream else ytdl.prepare_filename(data)
return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data) return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)
class MusicCog(commands.Cog): class AudioCog(commands.Cog):
def __init__(self, client): def __init__(self, bot):
self.client = client self.bot = bot
# Commands # Commands
@commands.command()
@commands.command() async def play(self, ctx, url):
async def play(self, ctx, url): """Streams from a url"""
"""Streams from a url"""
if ctx.author.voice is None:
if ctx.author.voice is None: return await ctx.send("You're not connected to a voice channel.")
return await ctx.send("You're not connected to a voice channel.")
try:
try: channel = ctx.author.voice.channel
channel = ctx.author.voice.channel await channel.connect()
await channel.connect() except:
except: pass
pass async with ctx.typing():
async with ctx.typing(): player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True)
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)
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}')
await ctx.send(f'Now playing: {player.title}')
@play.error
@play.error async def play_error(self, ctx, error):
async def play_error(self, ctx, error): if isinstance(error, commands.MissingRequiredArgument):
if isinstance(error, commands.MissingRequiredArgument): voice = ctx.voice_client
voice = ctx.voice_client if voice != None and voice.is_paused():
if voice != None and voice.is_paused(): voice.resume()
voice.resume() else:
else: await ctx.send("Missing URL!")
await ctx.send("Missing URL!")
@commands.command(aliases=['disconnect, dc'])
@commands.command(aliases=['disconnect, dc']) async def leave(self, ctx):
async def leave(self, ctx): """Disconnects bot from the voice channel"""
"""Disconnects bot from the voice channel"""
if ctx.author.voice is None:
if ctx.author.voice is None: return await ctx.send("You're not connected to a voice channel.")
return await ctx.send("You're not connected to a voice channel.")
voice = ctx.voice_client
voice = ctx.voice_client if voice is not None:
if voice is not None: if voice.is_connected():
if voice.is_connected(): await voice.disconnect()
await voice.disconnect() else:
else: await ctx.send("The bot is not connected to a voice channel.")
await ctx.send("The bot is not connected to a voice channel.")
@commands.command()
@commands.command() async def volume(self, ctx, volume: int):
async def volume(self, ctx, volume: int): """Changes the player's volume"""
"""Changes the player's volume"""
if ctx.author.voice is None:
if ctx.author.voice is None: return await ctx.send("You're not connected to a voice channel.")
return await ctx.send("You're not connected to a voice channel.")
if ctx.voice_client is None:
if ctx.voice_client is None: return await ctx.send("Not connected to a voice channel.")
return await ctx.send("Not connected to a voice channel.")
ctx.voice_client.source.volume = volume / 100
ctx.voice_client.source.volume = volume / 100 await ctx.send(f"Changed volume to {volume}%")
await ctx.send(f"Changed volume to {volume}%")
@commands.command()
@commands.command() async def pause(self, ctx):
async def pause(self, ctx): """Pauses audio"""
"""Pauses audio"""
if ctx.author.voice is None:
if ctx.author.voice is None: return await ctx.send("You're not connected to a voice channel.")
return await ctx.send("You're not connected to a voice channel.")
voice = ctx.voice_client
voice = ctx.voice_client if voice.is_playing():
if voice.is_playing(): voice.pause()
voice.pause() else:
else: await ctx.send("Currently no audio is playing.")
await ctx.send("Currently no audio is playing.")
@commands.command()
@commands.command() async def resume(self, ctx):
async def resume(self, ctx): """Resumes currently paused audio"""
"""Resumes currently paused audio"""
if ctx.author.voice is None:
if ctx.author.voice is None: return await ctx.send("You're not connected to a voice channel.")
return await ctx.send("You're not connected to a voice channel.")
voice = ctx.voice_client
voice = ctx.voice_client if voice.is_paused():
if voice.is_paused(): voice.resume()
voice.resume() else:
else: await ctx.send("The audio is not paused.")
await ctx.send("The audio is not paused.")
@commands.command()
@commands.command() async def stop(self, ctx):
async def stop(self, ctx): """Stops and disconnects the bot from voice"""
"""Stops and disconnects the bot from voice"""
if ctx.author.voice is None:
if ctx.author.voice is None: return await ctx.send("You're not connected to a voice channel.")
return await ctx.send("You're not connected to a voice channel.")
voice = ctx.voice_client
voice = ctx.voice_client if voice:
if voice: voice.stop()
voice.stop()
@commands.command(aliases=['join'])
@commands.command(aliases=['join']) async def connect(self, ctx):
async def connect(self, ctx): """Connects bot to currently connected voice channel"""
"""Connects bot to currently connected voice channel"""
if ctx.author.voice is None:
if ctx.author.voice is None: return await ctx.send("You're not connected to a voice channel.")
return await ctx.send("You're not connected to a voice channel.")
channel = ctx.author.voice.channel
channel = ctx.author.voice.channel try:
try: await channel.connect()
await channel.connect() except:
except: voice = ctx.voice_client
voice = ctx.voice_client if voice.is_connected() and voice.channel != channel:
if voice.is_connected() and voice.channel != channel: await voice.disconnect()
await voice.disconnect() await channel.connect()
await channel.connect()
def setup(bot):
def setup(client): bot.add_cog(AudioCog(bot))
client.add_cog(MusicCog(client))

View file

@ -1,59 +1,56 @@
import os import os
import discord import discord
import DiscordUtils import DiscordUtils
import datetime import datetime
from dateutil.parser import parse from dateutil.parser import parse
import requests import requests
from discord.ext import commands from discord.ext import commands
class CalendarCog(commands.Cog): class CalendarCog(commands.Cog):
def __init__(self, client): def __init__(self, bot):
self.client = client self.bot = bot
# Commands # Commands
@commands.command() @commands.command()
async def events(self, ctx): async def events(self, ctx):
"""Gets all upcoming events from Google Calendar""" """Gets all upcoming events from Google Calendar"""
# Check if environment variable exists # Check if environment variable exists
if not os.environ.get("GOOGLE_CALENDAR_ENDPOINT"): if not os.environ.get("GOOGLE_CALENDAR_ENDPOINT"):
return await ctx.send("No Google Calendar endpoint specified!") return await ctx.send("No Google Calendar endpoint specified!")
# Get upcoming events data from calendar # Get upcoming events data from calendar
current_time = datetime.datetime.utcnow() current_time = datetime.datetime.utcnow()
formatted_time = current_time.isoformat("T") + "Z" formatted_time = current_time.isoformat("T") + "Z"
calendar = os.environ.get("GOOGLE_CALENDAR_ENDPOINT") + "&timeMin=" + formatted_time calendar = os.environ.get("GOOGLE_CALENDAR_ENDPOINT") + "&timeMin=" + formatted_time
data = requests.get(calendar).json() data = requests.get(calendar).json()
# Check if there are any events # Check if there are any events
if not data["items"]: if not data["items"]:
return await ctx.send("There are no upcoming events!") return await ctx.send("There are no upcoming events!")
# Get all upcoming events as a list # Get all upcoming events as a list
list_of_events = data["items"] list_of_events = data["items"]
embeds = [] embeds = []
for event in list_of_events: for event in list_of_events:
# Create data set # Create data set
title = event["summary"] title = event["summary"]
start_time = event["start"]["dateTime"] start_time = event["start"]["dateTime"]
description = "No description." description = event["description"] if "description" in event else "No description."
if "description" in event: formatted_start_time = datetime.datetime.strftime(parse(start_time), format="%B %d, %Y")
description = event["description"] # Create embed for single event and add to embeds list
formatted_start_time = datetime.datetime.strftime(parse(start_time), format="%B %d, %Y") embed = discord.Embed(color=ctx.author.color, title=title, description=description)
embed.add_field(name="Starts On", value=formatted_start_time, inline=True)
# Create embed for single event and add to embeds list embeds.append(embed)
embed = discord.Embed(color=ctx.author.color, title=title, description=description)
embed.add_field(name="Starts On", value=formatted_start_time, inline=True) # Create paginator
embeds.append(embed) paginator = DiscordUtils.Pagination.CustomEmbedPaginator(ctx)
paginator.add_reaction('⏮️', "first")
# Create paginator paginator.add_reaction('', "back")
paginator = DiscordUtils.Pagination.CustomEmbedPaginator(ctx) paginator.add_reaction('', "next")
paginator.add_reaction('⏮️', "first") paginator.add_reaction('⏭️', "last")
paginator.add_reaction('', "back") await paginator.run(embeds)
paginator.add_reaction('', "next")
paginator.add_reaction('⏭️', "last") def setup(bot):
await paginator.run(embeds) bot.add_cog(CalendarCog(bot))
def setup(client):
client.add_cog(CalendarCog(client))

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 import discord
from discord.ext import commands from discord.ext import commands
class PingCog(commands.Cog): class PingCog(commands.Cog):
def __init__(self, client): def __init__(self, bot):
self.client = client self.bot = bot
# Commands # Commands
@commands.command(aliases=['latency']) @commands.command(aliases=['latency'])
async def ping(self, ctx): async def ping(self, ctx):
"""Returns the bot client latency""" """Returns the bot bot latency"""
await ctx.send(f'Pong! {round(self.client.latency * 1000)}ms') await ctx.send(f'Pong! {round(self.bot.latency * 1000)}ms')
def setup(client): def setup(bot):
client.add_cog(PingCog(client)) 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 os
import discord import discord
import DiscordUtils import DiscordUtils
import asyncio import asyncio
from google.cloud import firestore from google.cloud import firestore
from discord.ext import commands from discord.ext import commands
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
class InventoryCog(commands.Cog): class InventoryCog(commands.Cog):
def __init__(self, client): def __init__(self, bot):
self.client = client self.bot = bot
# Commands
# Commands
# Get a single item from inventory # Get a single item from inventory
@commands.command(aliases=['inv']) @commands.command(aliases=['inv'])
@commands.is_owner() @commands.is_owner()
async def inventory(self, ctx, arg1): async def inventory(self, ctx, arg1):
"""Get a single item from the inventory database""" """Get a single item from the inventory database"""
# Make single input embed # Make single input embed
def single_embed(item, id): def single_embed(item, id):
embed = discord.Embed(title=item["item_name"], description=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["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["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["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["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["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["quantity"] != "": embed.add_field(name="Quantity", value=item["quantity"], inline=True)
if item["box"] != "": embed.add_field(name="Box", value=item["box"], inline=True) if item["box"] != "": embed.add_field(name="Box", value=item["box"], inline=True)
return embed return embed
# Connect to Firestore DB inventory collection and get the item arg1 as doc # 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: if "INVENTORY_PROJECT_ID" in os.environ and "GOOGLE_APPLICATION_CREDENTIALS" in os.environ:
try: try:
db = firestore.AsyncClient(project=os.environ.get("INVENTORY_PROJECT_ID")) db = firestore.AsyncClient(project=os.environ.get("INVENTORY_PROJECT_ID"))
inventory_ref = db.collection("inventory").document(arg1) inventory_ref = db.collection("inventory").document(arg1)
doc = await inventory_ref.get() doc = await inventory_ref.get()
if doc.exists: if doc.exists:
await ctx.send(embed=single_embed(doc.to_dict(), doc.id)) await ctx.send(embed=single_embed(doc.to_dict(), doc.id))
else: else:
await ctx.send("That ID doesn't exist!") await ctx.send("That ID doesn't exist!")
except: except:
await ctx.send("The DB connection is not working.") await ctx.send("The DB connection is not working.")
elif "INVENTORY_PROJECT_ID" not in os.environ and "GOOGLE_APPLICATION_CREDENTIALS" in os.environ: elif "INVENTORY_PROJECT_ID" not in os.environ and "GOOGLE_APPLICATION_CREDENTIALS" in os.environ:
await ctx.send("Inventory Project ID not found!") await ctx.send("Inventory Project ID not found!")
elif "GOOGLE_APPLICATION_CREDENTIALS" not in os.environ: elif "GOOGLE_APPLICATION_CREDENTIALS" not in os.environ:
await ctx.send("GAPP Credentials not found!") await ctx.send("GAPP Credentials not found!")
@inventory.error @inventory.error
async def inventory_error(self, ctx, error): async def inventory_error(self, ctx, error):
if isinstance(error, commands.MissingRequiredArgument): if isinstance(error, commands.MissingRequiredArgument):
await ctx.send("Missing required argument! (e.g. d!inventory __10000__)") await ctx.send("Missing required argument! (e.g. d!inventory __10000__)")
if isinstance(error, commands.NotOwner): if isinstance(error, commands.NotOwner):
await ctx.send("You do not have permissions to run this command.") await ctx.send("You do not have permissions to run this command.")
def setup(client): def setup(bot):
client.add_cog(InventoryCog(client)) 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 import discord
from discord.ext import commands from discord.ext import commands
class ModerationCog(commands.Cog): class ModerationCog(commands.Cog):
def __init__(self, client): def __init__(self, bot):
self.client = client self.bot = bot
# Commands # Commands
@commands.command() @commands.command()
@commands.has_permissions(kick_members = True) @commands.has_permissions(kick_members = True)
async def kick(self, ctx, member : commands.MemberConverter, *, reason="No reason provided."): async def kick(self, ctx, member : commands.MemberConverter, *, reason="No reason provided."):
"""Kick a user from the server""" """Kick a user from the server"""
try: try:
await member.kick(reason=reason) await member.kick(reason=reason)
await ctx.send(member.mention + " has been kicked.") await ctx.send(member.mention + " has been kicked.")
except: 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}?") 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.command()
@commands.has_permissions(ban_members = True) @commands.has_permissions(ban_members = True)
async def ban(self, ctx, member : commands.MemberConverter, *, reason="No reason provided."): async def ban(self, ctx, member : commands.MemberConverter, *, reason="No reason provided."):
"""Ban a user from the server""" """Ban a user from the server"""
try: try:
await member.ban(reason=reason) await member.ban(reason=reason)
await ctx.send(member.mention + " has been banned.") await ctx.send(member.mention + " has been banned.")
except: 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}?") 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(client): def setup(bot):
client.add_cog(ModerationCog(client)) 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