diff options
Diffstat (limited to '')
-rwxr-xr-x | .gitignore | 2 | ||||
-rwxr-xr-x | .vscode/settings.json | 3 | ||||
-rwxr-xr-x | bot.py | 15 | ||||
-rwxr-xr-x | cogs/admin.py | 143 | ||||
-rwxr-xr-x | cogs/general.py | 648 | ||||
-rwxr-xr-x | cogs/help.py | 395 | ||||
-rwxr-xr-x | cogs/logs.py | 16 | ||||
-rwxr-xr-x | cogs/player.py | 455 | ||||
-rwxr-xr-x | cogs/src.py | 22 | ||||
-rwxr-xr-x | cogs/trans.py | 5 | ||||
-rwxr-xr-x | cogs/utils.py | 253 | ||||
-rwxr-xr-x | custom_commands.json | 14 | ||||
-rwxr-xr-x | main.py | 4 | ||||
-rw-r--r-- | readme.md | 24 |
14 files changed, 1271 insertions, 728 deletions
@@ -1,5 +1,5 @@ __pycache__ -config.py +config.json discord.log api_keys.json .vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100755 index 478c149..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{
- "python.pythonPath": "C:\\Users\\matei\\AppData\\Local\\Programs\\Python\\Python38-32\\python.exe"
-}
\ No newline at end of file @@ -1,16 +1,15 @@ from discord.ext import commands import discord import logging +import aiohttp import datetime -import config import json extensions = [ "cogs.utils", "cogs.admin", "cogs.src", - "cogs.help", "cogs.trans", "cogs.player", "cogs.general", @@ -36,6 +35,8 @@ class BedrockBot(commands.Bot): def __init__(self): super().__init__(command_prefix=get_prefix, case_insensitive=True) self.logger = logging.getLogger('discord') + self.messageBlacklist = [] + self.session = aiohttp.ClientSession() with open('custom_commands.json', 'r') as f: self.custom_commands = json.load(f) @@ -43,6 +44,10 @@ class BedrockBot(commands.Bot): for extension in extensions: self.load_extension(extension) + with open('config.json', 'r') as f: + self.config = json.load(f) + config = self.config + async def on_ready(self): self.uptime = datetime.datetime.utcnow() @@ -59,9 +64,7 @@ class BedrockBot(commands.Bot): async def on_message(self, message): - if message.author.bot: - return - if message.author.id in self.blacklist: + if message.author.bot or message.author.id in self.blacklist: return await self.process_commands(message) @@ -77,4 +80,4 @@ class BedrockBot(commands.Bot): return def run(self): - super().run(config.token, reconnect=True) + super().run(self.config["token"], reconnect=True) diff --git a/cogs/admin.py b/cogs/admin.py index 3cb7ff8..3a6299f 100755 --- a/cogs/admin.py +++ b/cogs/admin.py @@ -1,7 +1,6 @@ from discord.ext import commands import discord import asyncio -import subprocess import json import git import os @@ -12,30 +11,35 @@ class Admin(commands.Cog): async def is_mod(ctx): return ctx.author.guild_permissions.manage_channels - + + async def is_botmaster(ctx): + return ctx.author.id in ctx.bot.config[str(ctx.message.guild.id)]["bot_masters"] + @commands.command(aliases=['deleteEverything'], hidden=True) - @commands.check(is_mod) - async def purge(self, ctx, password): - if password == "MaxCantBeTrusted": - async for msg in ctx.channel.history(): - await msg.delete() + @commands.check(is_botmaster) + async def purge(self, ctx): + await ctx.message.channel.purge(limit=500) @commands.command(aliases=['quit'], hidden=True) - @commands.check(is_mod) - async def forceexit(self, ctx, password): - if password == "abort": - ctx.message.delete() - exit() + @commands.check(is_botmaster) + async def forceexit(self, ctx): + await ctx.send('Self Destructing') + await ctx.bot.close() @commands.command() @commands.check(is_mod) async def pull(self, ctx): + """Update the bot from github""" g = git.cmd.Git(os.getcwd()) - await ctx.send(f"Probably pulled.\n```bash\n{g.pull()}```") + try: + await ctx.send(f"Probably pulled.\n```bash\n{g.pull()}```") + except git.exc.GitCommandError as e: + await ctx.send(f"An error has occured when pulling```bash\n{e}```") @commands.command(aliases=['addcommand', 'newcommand']) @commands.check(is_mod) async def setcommand(self, ctx, command, *, message): + """Add a new simple command""" self.bot.custom_commands[ctx.prefix + command] = message with open('custom_commands.json', 'w') as f: json.dump(self.bot.custom_commands, f, indent=4) @@ -45,20 +49,20 @@ class Admin(commands.Cog): @commands.command(aliases=['deletecommand']) @commands.check(is_mod) async def removecommand(self, ctx, command): + """Remove a simple command""" del self.bot.custom_commands[ctx.prefix + command] with open('custom_commands.json', 'w') as f: json.dump(self.bot.custom_commands, f, indent=4) await ctx.send(f"Removed command {command}") - - - @commands.check(is_mod) + @commands.command(name='reload', hidden=True, usage='<extension>') + @commands.check(is_mod) async def _reload(self, ctx, ext): """Reloads an extension""" try: self.bot.reload_extension(f'cogs.{ext}') - await ctx.send(f'The extension {ext} was realoaded!') # Oceanlight told me too + await ctx.send(f'The extension {ext} was reloaded!') except commands.ExtensionNotFound: await ctx.send(f'The extension {ext} doesn\'t exist.') except commands.ExtensionNotLoaded: @@ -68,9 +72,9 @@ class Admin(commands.Cog): except commands.ExtensionFailed: await ctx.send(f'Some unknown error happened while trying to reload extension {ext} (check logs)') self.bot.logger.exception(f'Failed to reload extension {ext}:') - - @commands.check(is_mod) + @commands.command(name='load', hidden=True, usage='<extension>') + @commands.check(is_mod) async def _load(self, ctx, ext): """Loads an extension""" try: @@ -86,8 +90,8 @@ class Admin(commands.Cog): await ctx.send(f'Some unknown error happened while trying to reload extension {ext} (check logs)') self.bot.logger.exception(f'Failed to reload extension {ext}:') - @commands.check(is_mod) @commands.command(name='unload', hidden=True, usage='<extension>') + @commands.check(is_mod) async def _unload(self, ctx, ext): """Loads an extension""" try: @@ -101,27 +105,14 @@ class Admin(commands.Cog): await ctx.send(f'Some unknown error happened while trying to reload extension {ext} (check logs)') self.bot.logger.exception(f'Failed to unload extension {ext}:') - """ - @commands.command() - @commands.check(is_mod) - async def connect(self, ctx): - await ctx.author.voice.channel.connect() - await ctx.send(f"Joined channel {ctx.author.voice.channel.name}") - - @commands.command() - @commands.check(is_mod) - async def disconnect(self, ctx): - await ctx.voice_client.disconnect() - await ctx.send(f"Left channel {ctx.author.voice.channel.name}") - """ - @commands.command() @commands.check(is_mod) async def clear(self, ctx, number): + """Mass delete messages""" await ctx.message.channel.purge(limit=int(number)+1, check=None, before=None, after=None, around=None, oldest_first=False, bulk=True) - @commands.check(is_mod) @commands.command() + @commands.check(is_mod) async def mute(self, ctx, members: commands.Greedy[discord.Member]=False, mute_minutes: int = 0, *, reason: str = "absolutely no reason"): @@ -130,11 +121,11 @@ class Admin(commands.Cog): if not members: await ctx.send("You need to name someone to mute") return - elif type(members)=="str": - members = self.bot.get_user(int(user)) + elif type(members)==str: + members = [self.bot.get_user(int(members))] #muted_role = discord.utils.find(ctx.guild.roles, name="Muted") - muted_role = ctx.guild.get_role(707707894694412371) + muted_role = ctx.guild.get_role(int(self.bot.config[str(ctx.message.guild.id)]["mute_role"])) for member in members: if self.bot.user == member: # what good is a muted bot? embed = discord.Embed(title = "You can't mute me, I'm an almighty bot") @@ -148,31 +139,59 @@ class Admin(commands.Cog): for member in members: await member.remove_roles(muted_role, reason = "time's up ") - @commands.check(is_mod) @commands.command() + @commands.check(is_mod) async def unmute(self, ctx, members: commands.Greedy[discord.Member]): + """Remove the muted role""" if not members: await ctx.send("You need to name someone to unmute") return - elif type(members)=="str": + elif type(members)==str: members = self.bot.get_user(int(user)) - muted_role = ctx.guild.get_role(707707894694412371) + muted_role = ctx.guild.get_role(int(self.bot.config[str(ctx.message.guild.id)]["mute_role"])) for i in members: await i.remove_roles(muted_role) await ctx.send("{0.mention} has been unmuted by {1.mention}".format(i, ctx.author)) @commands.command() - @commands.check(is_mod) - async def logs(self, ctx, *, password): - if password == "beep boop": - await ctx.message.delete() - file = discord.File("discord.log") - await ctx.send(file=file) + @commands.check(is_botmaster) + async def ban(self, ctx, members: commands.Greedy[discord.Member]=False, + ban_minutes: int = 0, + *, reason: str = "absolutely no reason"): + """Mass ban members with an optional mute_minutes parameter to time it""" + + if not members: + await ctx.send("You need to name someone to ban") + return + elif type(members)==str: + members = [self.bot.get_user(int(members))] + for member in members: + if self.bot.user == member: # what good is a muted bot? + embed = discord.Embed(title = "You can't ban me, I'm an almighty bot") + await ctx.send(embed = embed) + continue + await member.send(f"You have been banned from {ctx.guild.name} for {mute_minutes} minutes because: ```{reason}```") + await ctx.guild.ban(member, reason=reason, delete_message_days=0) + await ctx.send("{0.mention} has been banned by {1.mention} for *{2}*".format(member, ctx.author, reason)) - @commands.command(aliases=['ban'], hidden=True) + if mute_minutes > 0: + await asyncio.sleep(ban_minutes * 60) + for member in members: + await ctx.guild.unban(member, reason="Time is up") + + @commands.command() + @commands.check(is_botmaster) + async def logs(self, ctx): + """Send the discord.log file""" + await ctx.message.delete() + file = discord.File("discord.log") + await ctx.send(file=file) + + @commands.command(hidden=True) @commands.check(is_mod) async def blacklist(self, ctx, members: commands.Greedy[discord.Member]=None): + """Ban someone from using the bot""" if not members: await ctx.send("You need to name someone to blacklist") return @@ -190,6 +209,34 @@ class Admin(commands.Cog): json.dump(self.bot.blacklist, f, indent=4) await ctx.send(f"{i} has been blacklisted.") + @commands.command() + @commands.check(is_mod) + async def activity(self, ctx, *, activity=None): + """Change the bot's activity""" + if activity: + game = discord.Game(activity) + else: + activity = "Mining away" + game = discord.Game(activity) + await self.bot.change_presence(activity=game) + await ctx.send(f"Activity changed to {activity}") + + @commands.command() + @commands.check(is_botmaster) + async def setvar(self, ctx, key, *, value): + """Set a config variable, ***use with caution**""" + with open('config.json', 'w') as f: + if value[0] == '[' and value[len(value)-1] == ']': + value = list(map(int, value[1:-1].split(','))) + self.bot.config[str(ctx.message.guild.id)][key] = value + json.dump(self.bot.config, f, indent=4) + + @commands.command() + @commands.check(is_mod) + async def printvar(self, ctx, key): + """Print a config variable, use for testing""" + await ctx.send(self.bot.config[str(ctx.message.guild.id)][key]) + def setup(bot): bot.add_cog(Admin(bot)) diff --git a/cogs/general.py b/cogs/general.py index ff60bb7..cf05eb4 100755 --- a/cogs/general.py +++ b/cogs/general.py @@ -1,6 +1,16 @@ from discord.ext import commands
import discord
import datetime
+import requests
+import json
+import dateutil.parser
+from random import randint
+import os, sys, inspect
+current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
+parent_dir = os.path.dirname(current_dir)
+sys.path.insert(0, parent_dir)
+import bot
+
def dump(obj):
output = ""
@@ -10,39 +20,419 @@ def dump(obj): return output
+class MyHelpCommand(commands.MinimalHelpCommand):
+ messages = [
+ "As seen on TV!",
+ "Awesome!",
+ "100% pure!",
+ "May contain nuts!",
+ "Better than Prey!",
+ "More polygons!",
+ "Sexy!",
+ "Limited edition!",
+ "Flashing letters!",
+ "Made by Notch!",
+ "It's here!",
+ "Best in class!",
+ "It's finished!",
+ "Kind of dragon free!",
+ "Excitement!",
+ "More than 500 sold!",
+ "One of a kind!",
+ "Heaps of hits on YouTube!",
+ "Indev!",
+ "Spiders everywhere!",
+ "Check it out!",
+ "Holy cow",
+ " man!",
+ "It's a game!",
+ "Made in Sweden!",
+ "Uses LWJGL!",
+ "Reticulating splines!",
+ "Minecraft!",
+ "Yaaay!",
+ "Singleplayer!",
+ "Keyboard compatible!",
+ "Undocumented!",
+ "Ingots!",
+ "Exploding creepers!",
+ "That's no moon!",
+ "l33t!",
+ "Create!",
+ "Survive!",
+ "Dungeon!",
+ "Exclusive!",
+ "The bee's knees!",
+ "Down with O.P.P.!",
+ "Closed source!",
+ "Classy!",
+ "Wow!",
+ "Not on steam!",
+ "Oh man!",
+ "Awesome community!",
+ "Pixels!",
+ "Teetsuuuuoooo!",
+ "Kaaneeeedaaaa!",
+ "Now with difficulty!",
+ "Enhanced!",
+ "90% bug free!",
+ "Pretty!",
+ "12 herbs and spices!",
+ "Fat free!",
+ "Absolutely no memes!",
+ "Free dental!",
+ "Ask your doctor!",
+ "Minors welcome!",
+ "Cloud computing!",
+ "Legal in Finland!",
+ "Hard to label!",
+ "Technically good!",
+ "Bringing home the bacon!",
+ "Indie!",
+ "GOTY!",
+ "Ceci n'est pas une title screen!",
+ "Euclidian!",
+ "Now in 3D!",
+ "Inspirational!",
+ "Herregud!",
+ "Complex cellular automata!",
+ "Yes",
+ " sir!",
+ "Played by cowboys!",
+ "OpenGL 1.2!",
+ "Thousands of colors!",
+ "Try it!",
+ "Age of Wonders is better!",
+ "Try the mushroom stew!",
+ "Sensational!",
+ "Hot tamale",
+ " hot hot tamale!",
+ "Play him off",
+ " keyboard cat!",
+ "Guaranteed!",
+ "Macroscopic!",
+ "Bring it on!",
+ "Random splash!",
+ "Call your mother!",
+ "Monster infighting!",
+ "Loved by millions!",
+ "Ultimate edition!",
+ "Freaky!",
+ "You've got a brand new key!",
+ "Water proof!",
+ "Uninflammable!",
+ "Whoa",
+ " dude!",
+ "All inclusive!",
+ "Tell your friends!",
+ "NP is not in P!",
+ "Notch <3 ez!",
+ "Music by C418!",
+ "Livestreamed!",
+ "Haunted!",
+ "Polynomial!",
+ "Terrestrial!",
+ "All is full of love!",
+ "Full of stars!",
+ "Scientific!",
+ "Cooler than Spock!",
+ "Collaborate and listen!",
+ "Never dig down!",
+ "Take frequent breaks!",
+ "Not linear!",
+ "Han shot first!",
+ "Nice to meet you!",
+ "Buckets of lava!",
+ "Ride the pig!",
+ "Larger than Earth!",
+ "sqrt(-1) love you!",
+ "Phobos anomaly!",
+ "Punching wood!",
+ "Falling off cliffs!",
+ "0% sugar!",
+ "150% hyperbole!",
+ "Synecdoche!",
+ "Let's danec!",
+ "Seecret Friday update!",
+ "Reference implementation!",
+ "Lewd with two dudes with food!",
+ "Kiss the sky!",
+ "20 GOTO 10!",
+ "Verlet intregration!",
+ "Peter Griffin!",
+ "Do not distribute!",
+ "Cogito ergo sum!",
+ "4815162342 lines of code!",
+ "A skeleton popped out!",
+ "The Work of Notch!",
+ "The sum of its parts!",
+ "BTAF used to be good!",
+ "I miss ADOM!",
+ "umop-apisdn!",
+ "OICU812!",
+ "Bring me Ray Cokes!",
+ "Finger-licking!",
+ "Thematic!",
+ "Pneumatic!",
+ "Sublime!",
+ "Octagonal!",
+ "Une baguette!",
+ "Gargamel plays it!",
+ "Rita is the new top dog!",
+ "SWM forever!",
+ "Representing Edsbyn!",
+ "Matt Damon!",
+ "Supercalifragilisticexpialidocious!",
+ "Consummate V's!",
+ "Cow Tools!",
+ "Double buffered!",
+ "Fan fiction!",
+ "Flaxkikare!",
+ "Jason! Jason! Jason!",
+ "Hotter than the sun!",
+ "Internet enabled!",
+ "Autonomous!",
+ "Engage!",
+ "Fantasy!",
+ "DRR! DRR! DRR!",
+ "Kick it root down!",
+ "Regional resources!",
+ "Woo",
+ " facepunch!",
+ "Woo",
+ " somethingawful!",
+ "Woo",
+ " /v/!",
+ "Woo",
+ " tigsource!",
+ "Woo",
+ " minecraftforum!",
+ "Woo",
+ " worldofminecraft!",
+ "Woo",
+ " reddit!",
+ "Woo",
+ " 2pp!",
+ "Google anlyticsed!",
+ "Now supports åäö!",
+ "Give us Gordon!",
+ "Tip your waiter!",
+ "Very fun!",
+ "12345 is a bad password!",
+ "Vote for net neutrality!",
+ "Lives in a pineapple under the sea!",
+ "MAP11 has two names!",
+ "Omnipotent!",
+ "Gasp!",
+ "...!",
+ "Bees",
+ " bees",
+ " bees",
+ " bees!",
+ "Jag känner en bot!",
+ "This text is hard to read if you play the game at the default resolution",
+ " but at 1080p it's fine!",
+ "Haha",
+ " LOL!",
+ "Hampsterdance!",
+ "Switches and ores!",
+ "Menger sponge!",
+ "idspispopd!",
+ "Eple (original edit)!",
+ "So fresh",
+ " so clean!",
+ "Slow acting portals!",
+ "Try the Nether!",
+ "Don't look directly at the bugs!",
+ "Oh",
+ " ok",
+ " Pigmen!",
+ "Finally with ladders!",
+ "Scary!",
+ "Play Minecraft",
+ " Watch Topgear",
+ " Get Pig!",
+ "Twittered about!",
+ "Jump up",
+ " jump up",
+ " and get down!",
+ "Joel is neat!",
+ "A riddle",
+ " wrapped in a mystery!",
+ "Huge tracts of land!",
+ "Welcome to your Doom!",
+ "Stay a while",
+ " stay forever!",
+ "Stay a while and listen!",
+ "Treatment for your rash!",
+ "\"Autological\" is!",
+ "Information wants to be free!",
+ "\"Almost never\" is an interesting concept!",
+ "Lots of truthiness!",
+ "The creeper is a spy!",
+ "Turing complete!",
+ "It's groundbreaking!",
+ "Let our battle's begin!",
+ "The sky is the limit!",
+ "Jeb has amazing hair!",
+ "Ryan also has amazing hair!",
+ "Casual gaming!",
+ "Undefeated!",
+ "Kinda like Lemmings!",
+ "Follow the train",
+ " CJ!",
+ "Leveraging synergy!",
+ "This message will never appear on the splash screen",
+ " isn't that weird?",
+ "DungeonQuest is unfair!",
+ "110813!",
+ "90210!",
+ "Check out the far lands!",
+ "Tyrion would love it!",
+ "Also try VVVVVV!",
+ "Also try Super Meat Boy!",
+ "Also try Terraria!",
+ "Also try Mount And Blade!",
+ "Also try Project Zomboid!",
+ "Also try World of Goo!",
+ "Also try Limbo!",
+ "Also try Pixeljunk Shooter!",
+ "Also try Braid!",
+ "That's super!",
+ "Bread is pain!",
+ "Read more books!",
+ "Khaaaaaaaaan!",
+ "Less addictive than TV Tropes!",
+ "More addictive than lemonade!",
+ "Bigger than a bread box!",
+ "Millions of peaches!",
+ "Fnord!",
+ "This is my true form!",
+ "Totally forgot about Dre!",
+ "Don't bother with the clones!",
+ "Pumpkinhead!",
+ "Hobo humping slobo babe!",
+ "Made by Jeb!",
+ "Has an ending!",
+ "Finally complete!",
+ "Feature packed!",
+ "Boots with the fur!",
+ "Stop",
+ " hammertime!",
+ "Testificates!",
+ "Conventional!",
+ "Homeomorphic to a 3-sphere!",
+ "Doesn't avoid double negatives!",
+ "Place ALL the blocks!",
+ "Does barrel rolls!",
+ "Meeting expectations!",
+ "PC gaming since 1873!",
+ "Ghoughpteighbteau tchoghs!",
+ "Déjà vu!",
+ "Déjà vu!",
+ "Got your nose!",
+ "Haley loves Elan!",
+ "Afraid of the big",
+ " black bat!",
+ "Doesn't use the U-word!",
+ "Child's play!",
+ "See you next Friday or so!",
+ "From the streets of Södermalm!",
+ "150 bpm for 400000 minutes!",
+ "Technologic!",
+ "Funk soul brother!",
+ "Pumpa kungen!",
+ "日本ハロー!",
+ "한국 안녕하세요!",
+ "Helo Cymru!",
+ "Cześć Polsko!",
+ "你好中国!",
+ "Привет Россия!",
+ "Γεια σου Ελλάδα!",
+ "My life for Aiur!",
+ "Lennart lennart = new Lennart();",
+ "I see your vocabulary has improved!",
+ "Who put it there?",
+ "You can't explain that!",
+ "if not ok then return end",
+ "§1C§2o§3l§4o§5r§6m§7a§8t§9i§ac",
+ "§kFUNKY LOL",
+ "SOPA means LOSER in Swedish!",
+ "Big Pointy Teeth!",
+ "Bekarton guards the gate!",
+ "Mmmph",
+ " mmph!",
+ "Don't feed avocados to parrots!",
+ "Swords for everyone!",
+ "Plz reply to my tweet!",
+ ".party()!",
+ "Take her pillow!",
+ "Put that cookie down!",
+ "Pretty scary!",
+ "I have a suggestion.",
+ "Now with extra hugs!",
+ "Now java 6!",
+ "Woah.",
+ "HURNERJSGER?",
+ "What's up",
+ " Doc?",
+ "Now contains 32 random daily cats!",
+ ""]
+ def get_command_signature(self, command):
+ return f'``{self.clean_prefix}{command.qualified_name} {command.signature}``'
+ def get_ending_note(self):
+ return self.messages[randint(0, len(self.messages)-1)]
+
class General(commands.Cog):
def __init__(self, bot):
self.bot = bot
+ self._original_help_command = bot.help_command
+ bot.help_command = MyHelpCommand()
+ bot.help_command.cog = self
+
+ def cog_unload(self):
+ self.bot.help_command = self._original_help_command
+
+ @commands.command()
+ async def prefix(self, ctx):
+ """Gets the bot prefixes"""
+ prefixes = bot.get_prefix(self.bot, ctx.message)
+ prefixes.pop(1)
+ prefixes.pop(1)
+ prefixes.pop(1)
+ output = ""
+ for i in prefixes:
+ output += i+", "
+
+ await ctx.send(f"My prefixes are {output}")
@commands.command()
async def userinfo(self, ctx, user: discord.Member=None):
+ """Get information about a user"""
#await ctx.send(f"```py\n{dump(user)}```")
if not user:
user = ctx.message.author
- elif type(user)=="str":
- user = self.bot.get_user(int(user))
- # Very very shit
- """
- await ctx.send(str(user.avatar_url))
- request.urlretrieve(str(user.avatar_url), "temp.webp")
- #filename = wget.download(user.avatar_url, out="temp.webp")
- image = Image.open("temp.webp").convert("RGB")
- image.save("temp.png", "PNG")
-
- f = discord.File("temp.png", filename="temp.png")
- #await messagable.send(file=f, embed=e)
- """
output = ""
for i in user.roles:
- output += i.mention
+ output += i.mention + " "
+ if user.color.value == 0:
+ color = 16777210
+ else:
+ color = user.color
- embed=discord.Embed(title=user.name, description=user.mention, color=user.color, timestamp=ctx.message.created_at)
+ if user.is_avatar_animated():
+ profilePic = user.avatar_url_as(format="gif")
+ else:
+ profilePic = user.avatar_url_as(format="png")
+
+ embed=discord.Embed(title=user.name, description=user.mention, color=color, timestamp=ctx.message.created_at)
+ if user.premium_since:
+ embed.add_field(name="Boosting since", value=user.premium_since.date())
#embed.set_thumbnail(url="attachment://temp.webp")
- embed.set_thumbnail(url=user.avatar_url)
- embed.set_image(url="attachment://temp.png")
+ embed.set_thumbnail(url=profilePic)
embed.add_field(name="Nickname", value=user.display_name, inline=False)
embed.add_field(name="Joined on", value=user.joined_at.date(), inline=True)
embed.add_field(name="Status", value=user.status, inline=True)
@@ -54,13 +444,14 @@ class General(commands.Cog): #os.remove("temp.png")
@commands.command()
- async def coop(self, ctx, user: discord.Member=None):
+ async def coop(self, ctx, *, user: discord.Member=None):
+ """Get the coop gang role"""
if not user:
user = ctx.message.author
- elif type(user)=="str":
+ else:
user = self.bot.get_user(int(user))
- coop_role = ctx.guild.get_role(694261282861219952)
+ coop_role = ctx.guild.get_role(int(self.bot.config[str(ctx.message.guild.id)]["coop_roleID"]))
if coop_role in user.roles:
await user.remove_roles(coop_role)
@@ -69,5 +460,222 @@ class General(commands.Cog): await user.add_roles(coop_role)
await ctx.send("You are now in the coop gang")
+ @commands.command()
+ async def serverinfo(self, ctx, guild=None):
+ """Get information about the server you are in"""
+ if not guild:
+ guild = ctx.message.guild
+ else:
+ print(type(guild))
+ guild = self.bot.get_guild(int(guild))
+
+ if guild.owner.color.value == 0:
+ color = 16777210
+ else:
+ color = guild.owner.color
+
+ emojiList = " "
+ for i in guild.emojis:
+ emojiList += str(i) + " "
+
+ if guild.is_icon_animated():
+ serverIcon = guild.icon_url_as(format="gif")
+ else:
+ serverIcon = guild.icon_url_as(format="png")
+
+ inactiveMembers = await guild.estimate_pruned_members(days=7)
+
+ embed=discord.Embed(title=guild.name, description=guild.description, color=color, timestamp=ctx.message.created_at)
+ if guild.premium_subscription_count == 0:
+ pass
+ else:
+ if guild.premium_subscription_count == 1:
+ embed.add_field(name="Boosted by:", value=f"{guild.premium_subscription_count} member", inline=True)
+ else:
+ embed.add_field(name="Boosted by:", value=f"{guild.premium_subscription_count} members", inline=True)
+ if guild.premium_subscribers:
+ boosters = ""
+ for i in guild.premium_subscribers:
+ boosters += i.mention+" "
+ embed.add_field(name="Boosted by:", value=boosters, inline=True)
+ embed.set_thumbnail(url=serverIcon)
+ embed.set_image(url=guild.splash_url_as(format="png"))
+ embed.add_field(name="Created on", value=guild.created_at.date(), inline=True)
+ embed.add_field(name="Members", value=guild.member_count, inline=True)
+ embed.add_field(name="Emojis", value=emojiList, inline=True)
+ embed.add_field(name="Owner", value=guild.owner.mention, inline=True)
+ embed.add_field(name="Members who haven't spoken in 7 days:", value=inactiveMembers, inline=True)
+ embed.set_footer(text=f"ID: {guild.id}")
+ await ctx.send(embed=embed)
+
+ @commands.command()
+ async def xboxuser(self, ctx, *, gamertag=None):
+ """Get information about an xbox live user"""
+ if not gamertag:
+ await ctx.send("You need to specify a gamer, gamer")
+ return
+
+ async with self.bot.session.get(f'https://xbl-api.prouser123.me/profile/gamertag/{gamertag}') as r:
+ gamer = json.loads(await r.text())
+
+ try:
+ await ctx.send(f"{gamer['error']}: {gamer['message']}")
+ return
+ except KeyError:
+ pass
+
+ for i in gamer["profileUsers"][0]["settings"]:
+ if i["id"] == "GameDisplayName":
+ gameName = i["value"]
+ continue
+ if i["id"] == "AppDisplayPicRaw":
+ picUrl = i["value"]
+ continue
+ if i["id"] == "Gamerscore":
+ Gamerscore = i["value"]+"<:gamerscore:727131234534424586>"
+ continue
+ if i["id"] == "AccountTier":
+ accountTier = i["value"]
+ continue
+ if i["id"] == "XboxOneRep":
+ reputation = i["value"]
+ continue
+ if i["id"] == "PreferredColor":
+ color = int(json.loads(requests.get(i["value"]).text)["primaryColor"], 16)
+ continue
+ if i["id"] == "Location":
+ location = i["value"]
+ continue
+ if i["id"] == "Bio":
+ #if len(i["value"]) == 0:
+ # Bio = "Unknown"
+ #else:
+ Bio = i["value"]
+ continue
+ if i["id"] == "Watermarks":
+ Watermarks = i["value"]
+ continue
+ if i["id"] == "RealName":
+ RealName = i["value"]
+ continue
+
+
+ embed=discord.Embed(title=gameName, description=Bio, color=color, timestamp=ctx.message.created_at)
+ embed.set_thumbnail(url=picUrl)
+ embed.add_field(name="Gamerscore", value=Gamerscore, inline=True)
+ if len(location) != 0:
+ embed.add_field(name="Location", value=location, inline=True)
+ if len(Watermarks) != 0:
+ embed.add_field(name="Watermarks", value=Watermarks, inline=True)
+ embed.add_field(name="Account Tier", value=accountTier, inline=True)
+ embed.add_field(name="Reputation", value=reputation, inline=True)
+ await ctx.send(embed=embed)
+
+ @commands.command(hidden=True)
+ async def xboxpresence(self, ctx, *, gamertag=None):
+ if not gamertag:
+ await ctx.send("You need to specify a gamer, gamer")
+ return
+
+ async with self.bot.session.get(f"https://xbl-api.prouser123.me/presence/gamertag/{gamertag}") as r:
+ gamer = json.loads(await r.text())
+
+ try:
+ await ctx.send(f"{gamer['error']}: {gamer['message']}")
+ return
+ except KeyError:
+ pass
+
+ state = gamer["state"]
+
+ try:
+ game = json.loads(requests.get(f"https://xbl-api.prouser123.me/titleinfo/{gamer['lastSeen']['titleId']}").text)
+ gameName = game["titles"][0]["name"]
+ gamePic = game["titles"][0]["images"][4]["url"]
+ timestamp = dateutil.parser.isoparse(gamer["lastSeen"]["timestamp"])
+ lastSeen = True
+ except Exception as e:
+ print(e)
+ lastSeen = False
+
+ if lastSeen:
+ embed=discord.Embed(title=gamer["gamertag"], description=state, timestamp=timestamp)
+ embed.set_thumbnail(url=gamePic)
+ embed.add_field(name="Game", value=gameName, inline=True)
+ await ctx.send(embed=embed)
+ else:
+ embed=discord.Embed(title=gamer["gamertag"], description=state, timestamp=ctx.message.created_at)
+ await ctx.send(embed=embed)
+
+ @commands.command()
+ async def compile(self, ctx, language=None, *, code=None):
+ """Compile code from a variety of programming languages, powered by <https://wandbox.org/>"""
+ """
+ listRequest = requests.get("https://wandbox.org/api/list.json")
+ compilerList = json.loads(listRequest.text)
+
+ for i in compilerList:
+ if i["language"] == language:
+ compiler = i["name"]
+ print(compiler)
+ """
+ compilers = {
+ "bash": "bash",
+ "c":"gcc-head-c",
+ "c#":"dotnetcore-head",
+ "coffeescript": "coffeescript-head",
+ "cpp": "gcc-head",
+ "elixir": "elixir-head",
+ "go": "go-head",
+ "java": "openjdk-head",
+ "javascript":"nodejs-head",
+ "lua": "lua-5.3.4",
+ "perl": "perl-head",
+ "php": "php-head",
+ "python":"cpython-3.8.0",
+ "ruby": "ruby-head",
+ "rust": "rust-head",
+ "sql": "sqlite-head",
+ "swift": "swift-5.0.1",
+ "typescript":"typescript-3.5.1",
+ "vim-script": "vim-head"
+ }
+ if not language:
+ await ctx.send(f"```json\n{json.dumps(compilers, indent=4)}```")
+ if not code:
+ await ctx.send("No code found")
+ return
+ try:
+ compiler = compilers[language.lower()]
+ except KeyError:
+ await ctx.send("Language not found")
+ return
+ body = {
+ "compiler": compiler,
+ "code": code,
+ "save": True
+ }
+ head = {
+ "Content-Type":"application/json"
+ }
+ async with ctx.typing():
+ async with self.bot.session.post("https://wandbox.org/api/compile.json", headers=head, data=json.dumps(body)) as r:
+ #r = requests.post("https://wandbox.org/api/compile.json", headers=head, data=json.dumps(body))
+ try:
+ response = json.loads(await r.text())
+ #await ctx.send(f"```json\n{json.dumps(response, indent=4)}```")
+ print(f"```json\n{json.dumps(response, indent=4)}```")
+ except json.decoder.JSONDecodeError:
+ await ctx.send(f"```json\n{r.text}```")
+
+ try:
+ embed=discord.Embed(title="Compiled code")
+ embed.add_field(name="Output", value=f'```{response["program_message"]}```', inline=False)
+ embed.add_field(name="Exit code", value=response["status"], inline=True)
+ embed.add_field(name="Link", value=f"[Permalink]({response['url']})", inline=True)
+ await ctx.send(embed=embed)
+ except KeyError:
+ await ctx.send(f"```json\n{json.dumps(response, indent=4)}```")
+
def setup(bot):
bot.add_cog(General(bot))
diff --git a/cogs/help.py b/cogs/help.py deleted file mode 100755 index 91c9c45..0000000 --- a/cogs/help.py +++ /dev/null @@ -1,395 +0,0 @@ -from discord.ext import commands
-from random import randint
-import os, sys, inspect
-current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
-parent_dir = os.path.dirname(current_dir)
-sys.path.insert(0, parent_dir)
-import bot
-
-class MyHelpCommand(commands.MinimalHelpCommand):
- messages = [
- "As seen on TV!",
- "Awesome!",
- "100% pure!",
- "May contain nuts!",
- "Better than Prey!",
- "More polygons!",
- "Sexy!",
- "Limited edition!",
- "Flashing letters!",
- "Made by Notch!",
- "It's here!",
- "Best in class!",
- "It's finished!",
- "Kind of dragon free!",
- "Excitement!",
- "More than 500 sold!",
- "One of a kind!",
- "Heaps of hits on YouTube!",
- "Indev!",
- "Spiders everywhere!",
- "Check it out!",
- "Holy cow",
- " man!",
- "It's a game!",
- "Made in Sweden!",
- "Uses LWJGL!",
- "Reticulating splines!",
- "Minecraft!",
- "Yaaay!",
- "Singleplayer!",
- "Keyboard compatible!",
- "Undocumented!",
- "Ingots!",
- "Exploding creepers!",
- "That's no moon!",
- "l33t!",
- "Create!",
- "Survive!",
- "Dungeon!",
- "Exclusive!",
- "The bee's knees!",
- "Down with O.P.P.!",
- "Closed source!",
- "Classy!",
- "Wow!",
- "Not on steam!",
- "Oh man!",
- "Awesome community!",
- "Pixels!",
- "Teetsuuuuoooo!",
- "Kaaneeeedaaaa!",
- "Now with difficulty!",
- "Enhanced!",
- "90% bug free!",
- "Pretty!",
- "12 herbs and spices!",
- "Fat free!",
- "Absolutely no memes!",
- "Free dental!",
- "Ask your doctor!",
- "Minors welcome!",
- "Cloud computing!",
- "Legal in Finland!",
- "Hard to label!",
- "Technically good!",
- "Bringing home the bacon!",
- "Indie!",
- "GOTY!",
- "Ceci n'est pas une title screen!",
- "Euclidian!",
- "Now in 3D!",
- "Inspirational!",
- "Herregud!",
- "Complex cellular automata!",
- "Yes",
- " sir!",
- "Played by cowboys!",
- "OpenGL 1.2!",
- "Thousands of colors!",
- "Try it!",
- "Age of Wonders is better!",
- "Try the mushroom stew!",
- "Sensational!",
- "Hot tamale",
- " hot hot tamale!",
- "Play him off",
- " keyboard cat!",
- "Guaranteed!",
- "Macroscopic!",
- "Bring it on!",
- "Random splash!",
- "Call your mother!",
- "Monster infighting!",
- "Loved by millions!",
- "Ultimate edition!",
- "Freaky!",
- "You've got a brand new key!",
- "Water proof!",
- "Uninflammable!",
- "Whoa",
- " dude!",
- "All inclusive!",
- "Tell your friends!",
- "NP is not in P!",
- "Notch <3 ez!",
- "Music by C418!",
- "Livestreamed!",
- "Haunted!",
- "Polynomial!",
- "Terrestrial!",
- "All is full of love!",
- "Full of stars!",
- "Scientific!",
- "Cooler than Spock!",
- "Collaborate and listen!",
- "Never dig down!",
- "Take frequent breaks!",
- "Not linear!",
- "Han shot first!",
- "Nice to meet you!",
- "Buckets of lava!",
- "Ride the pig!",
- "Larger than Earth!",
- "sqrt(-1) love you!",
- "Phobos anomaly!",
- "Punching wood!",
- "Falling off cliffs!",
- "0% sugar!",
- "150% hyperbole!",
- "Synecdoche!",
- "Let's danec!",
- "Seecret Friday update!",
- "Reference implementation!",
- "Lewd with two dudes with food!",
- "Kiss the sky!",
- "20 GOTO 10!",
- "Verlet intregration!",
- "Peter Griffin!",
- "Do not distribute!",
- "Cogito ergo sum!",
- "4815162342 lines of code!",
- "A skeleton popped out!",
- "The Work of Notch!",
- "The sum of its parts!",
- "BTAF used to be good!",
- "I miss ADOM!",
- "umop-apisdn!",
- "OICU812!",
- "Bring me Ray Cokes!",
- "Finger-licking!",
- "Thematic!",
- "Pneumatic!",
- "Sublime!",
- "Octagonal!",
- "Une baguette!",
- "Gargamel plays it!",
- "Rita is the new top dog!",
- "SWM forever!",
- "Representing Edsbyn!",
- "Matt Damon!",
- "Supercalifragilisticexpialidocious!",
- "Consummate V's!",
- "Cow Tools!",
- "Double buffered!",
- "Fan fiction!",
- "Flaxkikare!",
- "Jason! Jason! Jason!",
- "Hotter than the sun!",
- "Internet enabled!",
- "Autonomous!",
- "Engage!",
- "Fantasy!",
- "DRR! DRR! DRR!",
- "Kick it root down!",
- "Regional resources!",
- "Woo",
- " facepunch!",
- "Woo",
- " somethingawful!",
- "Woo",
- " /v/!",
- "Woo",
- " tigsource!",
- "Woo",
- " minecraftforum!",
- "Woo",
- " worldofminecraft!",
- "Woo",
- " reddit!",
- "Woo",
- " 2pp!",
- "Google anlyticsed!",
- "Now supports åäö!",
- "Give us Gordon!",
- "Tip your waiter!",
- "Very fun!",
- "12345 is a bad password!",
- "Vote for net neutrality!",
- "Lives in a pineapple under the sea!",
- "MAP11 has two names!",
- "Omnipotent!",
- "Gasp!",
- "...!",
- "Bees",
- " bees",
- " bees",
- " bees!",
- "Jag känner en bot!",
- "This text is hard to read if you play the game at the default resolution",
- " but at 1080p it's fine!",
- "Haha",
- " LOL!",
- "Hampsterdance!",
- "Switches and ores!",
- "Menger sponge!",
- "idspispopd!",
- "Eple (original edit)!",
- "So fresh",
- " so clean!",
- "Slow acting portals!",
- "Try the Nether!",
- "Don't look directly at the bugs!",
- "Oh",
- " ok",
- " Pigmen!",
- "Finally with ladders!",
- "Scary!",
- "Play Minecraft",
- " Watch Topgear",
- " Get Pig!",
- "Twittered about!",
- "Jump up",
- " jump up",
- " and get down!",
- "Joel is neat!",
- "A riddle",
- " wrapped in a mystery!",
- "Huge tracts of land!",
- "Welcome to your Doom!",
- "Stay a while",
- " stay forever!",
- "Stay a while and listen!",
- "Treatment for your rash!",
- "\"Autological\" is!",
- "Information wants to be free!",
- "\"Almost never\" is an interesting concept!",
- "Lots of truthiness!",
- "The creeper is a spy!",
- "Turing complete!",
- "It's groundbreaking!",
- "Let our battle's begin!",
- "The sky is the limit!",
- "Jeb has amazing hair!",
- "Ryan also has amazing hair!",
- "Casual gaming!",
- "Undefeated!",
- "Kinda like Lemmings!",
- "Follow the train",
- " CJ!",
- "Leveraging synergy!",
- "This message will never appear on the splash screen",
- " isn't that weird?",
- "DungeonQuest is unfair!",
- "110813!",
- "90210!",
- "Check out the far lands!",
- "Tyrion would love it!",
- "Also try VVVVVV!",
- "Also try Super Meat Boy!",
- "Also try Terraria!",
- "Also try Mount And Blade!",
- "Also try Project Zomboid!",
- "Also try World of Goo!",
- "Also try Limbo!",
- "Also try Pixeljunk Shooter!",
- "Also try Braid!",
- "That's super!",
- "Bread is pain!",
- "Read more books!",
- "Khaaaaaaaaan!",
- "Less addictive than TV Tropes!",
- "More addictive than lemonade!",
- "Bigger than a bread box!",
- "Millions of peaches!",
- "Fnord!",
- "This is my true form!",
- "Totally forgot about Dre!",
- "Don't bother with the clones!",
- "Pumpkinhead!",
- "Hobo humping slobo babe!",
- "Made by Jeb!",
- "Has an ending!",
- "Finally complete!",
- "Feature packed!",
- "Boots with the fur!",
- "Stop",
- " hammertime!",
- "Testificates!",
- "Conventional!",
- "Homeomorphic to a 3-sphere!",
- "Doesn't avoid double negatives!",
- "Place ALL the blocks!",
- "Does barrel rolls!",
- "Meeting expectations!",
- "PC gaming since 1873!",
- "Ghoughpteighbteau tchoghs!",
- "Déjà vu!",
- "Déjà vu!",
- "Got your nose!",
- "Haley loves Elan!",
- "Afraid of the big",
- " black bat!",
- "Doesn't use the U-word!",
- "Child's play!",
- "See you next Friday or so!",
- "From the streets of Södermalm!",
- "150 bpm for 400000 minutes!",
- "Technologic!",
- "Funk soul brother!",
- "Pumpa kungen!",
- "日本ハロー!",
- "한국 안녕하세요!",
- "Helo Cymru!",
- "Cześć Polsko!",
- "你好中国!",
- "Привет Россия!",
- "Γεια σου Ελλάδα!",
- "My life for Aiur!",
- "Lennart lennart = new Lennart();",
- "I see your vocabulary has improved!",
- "Who put it there?",
- "You can't explain that!",
- "if not ok then return end",
- "§1C§2o§3l§4o§5r§6m§7a§8t§9i§ac",
- "§kFUNKY LOL",
- "SOPA means LOSER in Swedish!",
- "Big Pointy Teeth!",
- "Bekarton guards the gate!",
- "Mmmph",
- " mmph!",
- "Don't feed avocados to parrots!",
- "Swords for everyone!",
- "Plz reply to my tweet!",
- ".party()!",
- "Take her pillow!",
- "Put that cookie down!",
- "Pretty scary!",
- "I have a suggestion.",
- "Now with extra hugs!",
- "Now java 6!",
- "Woah.",
- "HURNERJSGER?",
- "What's up",
- " Doc?",
- "Now contains 32 random daily cats!",
- ""]
- def get_command_signature(self, command):
- return f'``{self.clean_prefix}{command.qualified_name} {command.signature}``'
- def get_ending_note(self):
- return self.messages[randint(0, len(self.messages)-1)]
-
-class Help(commands.Cog):
- def __init__(self, bot):
- self.bot = bot
- self._original_help_command = bot.help_command
- bot.help_command = MyHelpCommand()
- bot.help_command.cog = self
-
- def cog_unload(self):
- self.bot.help_command = self._original_help_command
-
- @commands.command()
- async def prefix(self, ctx):
- prefixes = bot.get_prefix(self.bot, ctx.message)
- prefixes.pop(1)
- prefixes.pop(1)
- prefixes.pop(1)
- output = ""
- for i in prefixes:
- output += i+", "
-
- await ctx.send(f"My prefixes are {output}")
-
-def setup(bot):
- bot.add_cog(Help(bot))
diff --git a/cogs/logs.py b/cogs/logs.py index ea3870a..c1b3a49 100755 --- a/cogs/logs.py +++ b/cogs/logs.py @@ -8,12 +8,19 @@ class Logs(commands.Cog): @commands.Cog.listener() async def on_message_delete(self, message): + if (message.id in self.bot.messageBlacklist): + self.bot.messageBlacklist.remove(message.id) + return if message.guild.id != 574267523869179904: return - channel = self.bot.get_channel(718187032869994686) + if message.author.color.value == 0: + color = 16777210 + else: + color = message.author.color + channel = self.bot.get_channel(int(self.bot.config[str(message.guild.id)]["logs_channel"])) embed = discord.Embed( title='Deleted Message', - color=message.author.color, + color=color, timestamp=message.created_at ) embed.add_field( @@ -25,9 +32,12 @@ class Logs(commands.Cog): @commands.Cog.listener() async def on_message_edit(self, before, after): + if before.content == after.content: + return + if after.guild.id != 574267523869179904: return - channel = self.bot.get_channel(718187032869994686) + channel = self.bot.get_channel(int(self.bot.config[str(before.guild.id)]["logs_channel"])) if before.author.color.value == 0: color = 16777210 else: diff --git a/cogs/player.py b/cogs/player.py index 5c046a7..7013e6d 100755 --- a/cogs/player.py +++ b/cogs/player.py @@ -1,18 +1,38 @@ -import asyncio
+"""
+Please understand Music bots are complex, and that even this basic example can be daunting to a beginner.
+
+For this reason it's highly advised you familiarize yourself with discord.py, python and asyncio, BEFORE
+you attempt to write a music bot.
+
+This example makes use of: Python 3.6
+
+For a more basic voice example please read:
+ https://github.com/Rapptz/discord.py/blob/rewrite/examples/basic_voice.py
+
+This is a very basic playlist example, which allows per guild playback of unique queues.
+The commands implement very basic logic for basic usage. But allow for expansion. It would be advisable to implement
+your own permissions and usage logic for commands.
+
+e.g You might like to implement a vote before skipping the song or only allow admins to stop the player.
+
+Music bots require lots of work, and tuning. Goodluck.
+If you find any bugs feel free to ping me on discord. @Eviee#0666
+"""
import discord
-import youtube_dl
from discord.ext import commands
-from discord.ext import tasks
-from pathlib import Path
-
-# Straight up copied from
-# https://github.com/Rapptz/discord.py/blob/master/examples/basic_voice.py
-# Suppress noise about console usage from errors
-youtube_dl.utils.bug_reports_message = lambda: ''
+import asyncio
+import itertools
+import sys
+import traceback
+from async_timeout import timeout
+from functools import partial
+from youtube_dl import YoutubeDL
+from pathlib import Path
+from discord.ext import tasks
-ytdl_format_options = {
+ytdlopts = {
'format': 'bestaudio/best',
'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s',
'restrictfilenames': True,
@@ -23,117 +43,396 @@ ytdl_format_options = { 'quiet': True,
'no_warnings': True,
'default_search': 'auto',
- 'source_address': '0.0.0.0' # bind to ipv4 since ipv6 addresses cause issues sometimes
+ 'source_address': '0.0.0.0' # ipv6 addresses cause issues sometimes
}
-ffmpeg_options = {
- 'options': '-vn'
+ffmpegopts = {
+ 'before_options': '-nostdin',
+ 'options': '-vn -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5'
}
-ytdl = youtube_dl.YoutubeDL(ytdl_format_options)
+ytdl = YoutubeDL(ytdlopts)
+
+
+class VoiceConnectionError(commands.CommandError):
+ """Custom Exception class for connection errors."""
+
+
+class InvalidVoiceChannel(VoiceConnectionError):
+ """Exception for cases of invalid Voice Channels."""
class YTDLSource(discord.PCMVolumeTransformer):
- def __init__(self, source, *, data, volume=0.5):
- super().__init__(source, volume)
- self.data = data
+ def __init__(self, source, *, data, requester):
+ super().__init__(source)
+ self.requester = requester
self.title = data.get('title')
- self.url = data.get('url')
+ self.web_url = data.get('webpage_url')
+
+ # YTDL info dicts (data) have other useful information you might want
+ # https://github.com/rg3/youtube-dl/blob/master/README.md
+
+ def __getitem__(self, item: str):
+ """Allows us to access attributes similar to a dict.
+
+ This is only useful when you are NOT downloading.
+ """
+ return self.__getattribute__(item)
@classmethod
- async def from_url(cls, url, *, loop=None, stream=False):
+ async def create_source(cls, ctx, search: str, *, loop, download=True):
loop = loop or asyncio.get_event_loop()
- data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))
+
+ to_run = partial(ytdl.extract_info, url=search, download=download)
+ data = await loop.run_in_executor(None, to_run)
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)
+ await ctx.send(f'```ini\n[Added {data["title"]} to the Queue.]\n```', delete_after=15)
+
+ if download:
+ source = ytdl.prepare_filename(data)
+ else:
+ return {'webpage_url': data['webpage_url'], 'requester': ctx.author, 'title': data['title']}
+
+ return cls(discord.FFmpegPCMAudio(source, **ffmpegopts), data=data, requester=ctx.author)
+
+ @classmethod
+ async def regather_stream(cls, data, *, loop):
+ """Used for preparing a stream, instead of downloading.
+
+ Since Youtube Streaming links expire."""
+ loop = loop or asyncio.get_event_loop()
+ requester = data['requester']
+
+ to_run = partial(ytdl.extract_info, url=data['webpage_url'], download=False)
+ data = await loop.run_in_executor(None, to_run)
+
+ return cls(discord.FFmpegPCMAudio(data['url'], **ffmpegopts), data=data, requester=requester)
+
+
+class MusicPlayer:
+ """A class which is assigned to each guild using the bot for Music.
+
+ This class implements a queue and loop, which allows for different guilds to listen to different playlists
+ simultaneously.
+
+ When the bot disconnects from the Voice it's instance will be destroyed.
+ """
+
+ __slots__ = ('bot', '_guild', '_channel', '_cog', 'queue', 'next', 'current', 'np', 'volume')
+
+ def __init__(self, ctx):
+ self.bot = ctx.bot
+ self._guild = ctx.guild
+ self._channel = ctx.channel
+ self._cog = ctx.cog
+
+ self.queue = asyncio.Queue()
+ self.next = asyncio.Event()
+
+ self.np = None # Now playing message
+ self.volume = .5
+ self.current = None
+
+ ctx.bot.loop.create_task(self.player_loop())
+
+ async def player_loop(self):
+ """Our main player loop."""
+ await self.bot.wait_until_ready()
+
+ while not self.bot.is_closed():
+ self.next.clear()
+
+ try:
+ # Wait for the next song. If we timeout cancel the player and disconnect...
+ async with timeout(300): # 5 minutes...
+ source = await self.queue.get()
+ except asyncio.TimeoutError:
+ if self in self._cog.players.values():
+ return self.destroy(self._guild)
+ return
+
+ if not isinstance(source, YTDLSource):
+ # Source was probably a stream (not downloaded)
+ # So we should regather to prevent stream expiration
+ try:
+ source = await YTDLSource.regather_stream(source, loop=self.bot.loop)
+ except Exception as e:
+ await self._channel.send(f'There was an error processing your song.\n'
+ f'```css\n[{e}]\n```')
+ continue
+
+ source.volume = self.volume
+ self.current = source
+
+ self._guild.voice_client.play(source, after=lambda _: self.bot.loop.call_soon_threadsafe(self.next.set))
+ self.np = await self._channel.send(f'**Now Playing:** `{source.title}` requested by '
+ f'`{source.requester}`')
+ await self.next.wait()
+
+ # Make sure the FFmpeg process is cleaned up.
+ source.cleanup()
+ self.current = None
+
+ try:
+ # We are no longer playing this song...
+ await self.np.delete()
+ except discord.HTTPException:
+ pass
+
+ def destroy(self, guild):
+ """Disconnect and cleanup the player."""
+ return self.bot.loop.create_task(self._cog.cleanup(guild))
class Player(commands.Cog):
+ """Music related commands."""
+
+ __slots__ = ('bot', 'players')
+
def __init__(self, bot):
self.bot = bot
self.cleanup.start()
+ self.players = {}
- async def is_in_vc(ctx):
+ async def cleanup(self, guild):
try:
- return ctx.author.voice.channel
+ await guild.voice_client.disconnect()
except AttributeError:
- return False
+ pass
+
+ try:
+ for entry in self.players[guild.id].queue._queue:
+ if isinstance(entry, YTDLSource):
+ entry.cleanup()
+ self.players[guild.id].queue._queue.clear()
+ except KeyError:
+ pass
+
+ try:
+ del self.players[guild.id]
+ except KeyError:
+ pass
+
+ async def __local_check(self, ctx):
+ """A local check which applies to all commands in this cog."""
+ if not ctx.guild:
+ raise commands.NoPrivateMessage
+ return True
+
+ async def __error(self, ctx, error):
+ """A local error handler for all errors arising from commands in this cog."""
+ if isinstance(error, commands.NoPrivateMessage):
+ try:
+ return await ctx.send('This command can not be used in Private Messages.')
+ except discord.HTTPException:
+ pass
+ elif isinstance(error, InvalidVoiceChannel):
+ await ctx.send('Error connecting to Voice Channel. '
+ 'Please make sure you are in a valid channel or provide me with one')
+
+ print('Ignoring exception in command {}:'.format(ctx.command), file=sys.stderr)
+ traceback.print_exception(type(error), error, error.__traceback__, file=sys.stderr)
+
+ def get_player(self, ctx):
+ """Retrieve the guild player, or generate one."""
+ try:
+ player = self.players[ctx.guild.id]
+ except KeyError:
+ player = MusicPlayer(ctx)
+ self.players[ctx.guild.id] = player
+
+ return player
+
+ @commands.command(name='connectvc', aliases=['join'])
+ async def connect_(self, ctx, *, channel: discord.VoiceChannel=None):
+ """Connect to voice.
+
+ Parameters
+ ------------
+ channel: discord.VoiceChannel [Optional]
+ The channel to connect to. If a channel is not specified, an attempt to join the voice channel you are in
+ will be made.
+
+ This command also handles moving the bot to different channels.
+ """
+ if not channel:
+ try:
+ channel = ctx.author.voice.channel
+ except AttributeError:
+ raise InvalidVoiceChannel('No channel to join. Please either specify a valid channel or join one.')
+
+ vc = ctx.voice_client
+
+ if vc:
+ if vc.channel.id == channel.id:
+ return
+ try:
+ await vc.move_to(channel)
+ except asyncio.TimeoutError:
+ raise VoiceConnectionError(f'Moving to channel: <{channel}> timed out.')
+ else:
+ try:
+ await channel.connect()
+ except asyncio.TimeoutError:
+ raise VoiceConnectionError(f'Connecting to channel: <{channel}> timed out.')
+
+ await ctx.send(f'Connected to: **{channel}**', delete_after=20)
+
+ @commands.command(name='play', aliases=['sing'])
+ async def play_(self, ctx, *, search: str):
+ """Request a song and add it to the queue.
+
+ This command attempts to join a valid voice channel if the bot is not already in one.
+ Uses YTDL to automatically search and retrieve a song.
+
+ Parameters
+ ------------
+ search: str [Required]
+ The song to search and retrieve using YTDL. This could be a simple search, an ID or URL.
+ """
+ await ctx.trigger_typing()
+
+ vc = ctx.voice_client
+
+ if not vc:
+ await ctx.invoke(self.connect_)
- @commands.check(is_in_vc)
- @commands.command()
- async def join(self, ctx, *, channel: discord.VoiceChannel):
- """Joins a voice channel"""
+ player = self.get_player(ctx)
- if ctx.voice_client is not None:
- return await ctx.voice_client.move_to(channel)
+ # If download is False, source will be a dict which will be used later to regather the stream.
+ # If download is True, source will be a discord.FFmpegPCMAudio with a VolumeTransformer.
+ source = await YTDLSource.create_source(ctx, search, loop=self.bot.loop, download=False)
- await channel.connect()
- @commands.command()
- async def play(self, ctx, *, query):
- """Plays a file from the local filesystem"""
+ await player.queue.put(source)
- source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(query))
- ctx.voice_client.play(source, after=lambda e: print('Player error: %s' % e) if e else None)
+ @commands.command(name='pause')
+ async def pause_(self, ctx):
+ """Pause the currently playing song."""
+ vc = ctx.voice_client
- await ctx.send('Now playing: {}'.format(query))
- @commands.command()
- async def yt(self, ctx, *, url):
- """Plays from a url (almost anything youtube_dl supports)"""
+ if not vc or not vc.is_playing():
+ return await ctx.send('I am not currently playing anything!', delete_after=20)
+ elif vc.is_paused():
+ return
+
+ vc.pause()
+ await ctx.send(f'**`{ctx.author}`**: Paused the song!')
+
+ @commands.command(name='resume')
+ async def resume_(self, ctx):
+ """Resume the currently paused song."""
+ vc = ctx.voice_client
+
+ if not vc or not vc.is_connected():
+ return await ctx.send('I am not currently playing anything!', delete_after=20)
+ elif not vc.is_paused():
+ return
+
+ vc.resume()
+ await ctx.send(f'**`{ctx.author}`**: Resumed the song!')
+
+ @commands.command(name='skip')
+ async def skip_(self, ctx):
+ """Skip the song."""
+ vc = ctx.voice_client
+
+ if not vc or not vc.is_connected():
+ return await ctx.send('I am not currently playing anything!', delete_after=20)
+
+ if vc.is_paused():
+ pass
+ elif not vc.is_playing():
+ return
+
+ vc.stop()
+ await ctx.send(f'**`{ctx.author}`**: Skipped the song!')
+
+ @commands.command(name='queue', aliases=['q', 'playlist'])
+ async def queue_info(self, ctx):
+ """Retrieve a basic queue of upcoming songs."""
+ vc = ctx.voice_client
+
+ if not vc or not vc.is_connected():
+ return await ctx.send('I am not currently connected to voice!', delete_after=20)
+
+ player = self.get_player(ctx)
+ if player.queue.empty():
+ return await ctx.send('There are currently no more queued songs.')
+
+ # Grab up to 5 entries from the queue...
+ upcoming = list(itertools.islice(player.queue._queue, 0, 5))
+
+ fmt = '\n'.join(f'**`{_["title"]}`**' for _ in upcoming)
+ embed = discord.Embed(title=f'Upcoming - Next {len(upcoming)}', description=fmt)
+
+ await ctx.send(embed=embed)
+
+ @commands.command(name='now_playing', aliases=['np', 'current', 'currentsong', 'playing'])
+ async def now_playing_(self, ctx):
+ """Display information about the currently playing song."""
+ vc = ctx.voice_client
+
+ if not vc or not vc.is_connected():
+ return await ctx.send('I am not currently connected to voice!', delete_after=20)
+
+ player = self.get_player(ctx)
+ if not player.current:
+ return await ctx.send('I am not currently playing anything!')
+
+ try:
+ # Remove our previous now_playing message.
+ await player.np.delete()
+ except discord.HTTPException:
+ pass
- async with ctx.typing():
- player = await YTDLSource.from_url(url, loop=self.bot.loop)
- ctx.voice_client.play(player, after=lambda e: print('Player error: %s' % e) if e else None)
+ player.np = await ctx.send(f'**Now Playing:** `{vc.source.title}` '
+ f'requested by `{vc.source.requester}`')
- await ctx.send('Now playing: {}'.format(player.title))
+ @commands.command(name='volume', aliases=['vol'])
+ async def change_volume(self, ctx, *, vol: float):
+ """Change the player volume.
- @commands.command()
- async def stream(self, ctx, *, url):
- """Streams from a url (same as yt, but doesn't predownload)"""
+ Parameters
+ ------------
+ volume: float or int [Required]
+ The volume to set the player to in percentage. This must be between 1 and 100.
+ """
+ vc = ctx.voice_client
- 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('Player error: %s' % e) if e else None)
+ if not vc or not vc.is_connected():
+ return await ctx.send('I am not currently connected to voice!', delete_after=20)
- await ctx.send('Now playing: {}'.format(player.title))
+ if not 0 < vol < 101:
+ return await ctx.send('Please enter a value between 1 and 100.')
- @commands.check(is_in_vc)
- @commands.command()
- async def volume(self, ctx, volume: int):
- """Changes the player's volume"""
+ player = self.get_player(ctx)
- if ctx.voice_client is None:
- return await ctx.send("Not connected to a voice channel.")
+ if vc.source:
+ vc.source.volume = vol / 100
- ctx.voice_client.source.volume = volume / 100
- await ctx.send("Changed volume to {}%".format(volume))
+ player.volume = vol / 100
+ await ctx.send(f'**`{ctx.author}`**: Set the volume to **{vol}%**')
- @commands.check(is_in_vc)
- @commands.command()
- async def stop(self, ctx):
- """Stops and disconnects the bot from voice"""
+ @commands.command(name='stop')
+ async def stop_(self, ctx):
+ """Stop the currently playing song and destroy the player.
- await ctx.voice_client.disconnect()
+ !Warning!
+ This will destroy the player assigned to your guild, also deleting any queued songs and settings.
+ """
+ vc = ctx.voice_client
- @play.before_invoke
- @yt.before_invoke
- @stream.before_invoke
- async def ensure_voice(self, ctx):
- if ctx.voice_client is None:
- if ctx.author.voice:
- await ctx.author.voice.channel.connect()
- else:
- await ctx.send("You are not connected to a voice channel.")
- raise commands.CommandError("Author not connected to a voice channel.")
- elif ctx.voice_client.is_playing():
- ctx.voice_client.stop()
+ if not vc or not vc.is_connected():
+ return await ctx.send('I am not currently playing anything!', delete_after=20)
+ await self.cleanup(ctx.guild)
+
@tasks.loop(hours=10.0)
async def cleanup(self):
for p in Path("./downloads/").glob("*"):
@@ -141,4 +440,4 @@ class Player(commands.Cog): def setup(bot):
- bot.add_cog(Player(bot))
\ No newline at end of file + bot.add_cog(Player(bot))
diff --git a/cogs/src.py b/cogs/src.py index 96bf53c..bf776e7 100755 --- a/cogs/src.py +++ b/cogs/src.py @@ -63,11 +63,6 @@ async def deleteRun(self, apiKey, ctx, run): await ctx.send("Something went wrong") await ctx.message.author.send(f"```json\n{json.dumps(json.loads(r.text),indent=4)}```") -async def clear(self): - async for msg in self.bot.get_channel(699713639866957905).history(): - await msg.delete() - - async def pendingRuns(self, ctx): mcbe_runs = 0 mcbeil_runs = 0 @@ -124,11 +119,11 @@ async def pendingRuns(self, ctx): mcbece_runs += 1 embed = discord.Embed( title=leaderboard, url=link, description=f"{categoryName} in `{str(rta).replace('000','')}` by **{player}**", color=16711680+i*60, timestamp=timestamp) - await self.bot.get_channel(699713639866957905).send(embed=embed) + await self.bot.get_channel(int(self.bot.config[str(ctx.message.guild.id)]["pending_channel"])).send(embed=embed) runs = runs2 gameID = gameID2 embed_stats = discord.Embed(title='Pending Run Stats', description=f"Full Game Runs: {mcbe_runs}\nIndividual Level Runs: {mcbeil_runs}\nCategory Extension Runs: {mcbece_runs}", color=16711680 + i * 60) - await self.bot.get_channel(699713639866957905).send(embed=embed_stats) + await self.bot.get_channel(int(self.bot.config[str(ctx.message.guild.id)]["pending_channel"])).send(embed=embed_stats) async def verifyNew(self, apiKey=None, userID=None): @@ -176,7 +171,7 @@ async def verifyNew(self, apiKey=None, userID=None): wrCounter = False runnerCounter = False - + for i in pbs["data"]: if i["place"] == 1: if i["run"]["game"] == "yd4ovvg1" or i["run"]["game"] == "v1po7r76": @@ -208,7 +203,7 @@ class Src(commands.Cog): @commands.guild_only() async def pending(self, ctx): async with ctx.typing(): - await clear(self) + await self.bot.get_channel(int(self.bot.config[str(ctx.message.guild.id)]["pending_channel"])).purge(limit=500) await pendingRuns(self, ctx) @commands.command(description="Reject runs quickly") @@ -231,9 +226,12 @@ class Src(commands.Cog): @commands.command() async def verify(self, ctx, apiKey=None, userID=None): - #if apiKey == None: - # await ctx.send(f"Please try again this command by getting an apiKey from https://www.speedrun.com/api/auth then do `{ctx.prefix}verify <apiKey>` in my DMs or anywhere in this server. \nBe careful who you share this key with. To learn more check out https://github.com/speedruncomorg/api/blob/master/authentication.md") - # return + self.bot.messageBlacklist.append(ctx.message.id) + if apiKey == None: + data = json.loads(Path('./api_keys.json').read_text()) + if not str(ctx.author.id) in data: + await ctx.send(f"Please try again this command by getting an apiKey from https://www.speedrun.com/api/auth then do `{ctx.prefix}verify <apiKey>` in my DMs or anywhere in this server. \nBe careful who you share this key with. To learn more check out https://github.com/speedruncomorg/api/blob/master/authentication.md") + return if ctx.guild != None: await ctx.message.delete() async with ctx.typing(): diff --git a/cogs/trans.py b/cogs/trans.py index 2c6ed71..9ca1004 100755 --- a/cogs/trans.py +++ b/cogs/trans.py @@ -20,6 +20,7 @@ async def translateMsg(text, target="en"): result['translatedText'] = result['translatedText'].replace("'", "'") result['translatedText'] = result['translatedText'].replace(""", '"') result['translatedText'] = result['translatedText'].replace("<@! ", "<@!") + result['translatedText'] = result['translatedText'].replace("<@ ", "<@") result['translatedText'] = result['translatedText'].replace("<# ", "<#") return result; @@ -28,8 +29,9 @@ class Trans(commands.Cog): def __init__(self, bot): self.bot = bot - @commands.command(help="Translate text in english (using google translate)", brief="Translate to english") + @commands.command(help="Translate text in english (using google translate)", brief="Translate to english", aliases=["翻译", "脑热", "动漫"]) async def translate(self, ctx, *, message): + """Translate to english""" response = await translateMsg(message) embed=discord.Embed(title="Translation",description=f"{ctx.message.author.mention} says:", timestamp=ctx.message.created_at, color=0x4d9aff) embed.add_field(name=f"[{response['detectedSourceLanguage']}] Source:" , value=response['input'], inline=False) @@ -38,6 +40,7 @@ class Trans(commands.Cog): @commands.command() async def trans(self, ctx, lan, *, message): + """Translate to a specific language""" response = await translateMsg(message, lan) embed=discord.Embed(title="Translation",description=f"{ctx.message.author.mention} says:", timestamp=ctx.message.created_at, color=0x4d9aff) embed.add_field(name=f"[{response['detectedSourceLanguage']}] Source:" , value=response['input'], inline=False) diff --git a/cogs/utils.py b/cogs/utils.py index 27fd708..bacf5e6 100755 --- a/cogs/utils.py +++ b/cogs/utils.py @@ -1,9 +1,7 @@ from discord.ext import commands from discord.ext import tasks import discord -import requests import json -import asyncio import datetime # forgot to import this and ended up looking mentally unstable # troll literally pointed out atleast 4 things I did wrong in 3 lines of code @@ -16,6 +14,7 @@ from selenium.webdriver.chrome.options import Options #import image as Image from PIL import Image from PIL import ImageFilter +import functools def set_viewport_size(driver, width, height): window_size = driver.execute_script(""" @@ -25,16 +24,55 @@ def set_viewport_size(driver, width, height): driver.set_window_size(*window_size) async def reportStuff(self, ctx, message): - channel = self.bot.get_channel(715549209998262322) + channel = self.bot.get_channel(int(self.bot.config[str(ctx.message.guild.id)]["report_channel"])) embed = discord.Embed( title=f"Report from {ctx.message.author}", - description=f"{message}", + description=f"{message}", color=ctx.message.author.color, timestamp=ctx.message.created_at) await channel.send(embed=embed) await ctx.author.send("Report has been submitted") +def save_leaderboard(): + DRIVER = '/usr/lib/chromium-browser/chromedriver' + chrome_options = Options() + chrome_options.add_argument("--disable-dev-shm-usage") + chrome_options.add_argument("--headless") + chrome_options.add_argument("--no-sandbox") + chrome_options.add_argument("--disable-gpu") + #chrome_options.binary_location = "" + driver = webdriver.Chrome(DRIVER, chrome_options=chrome_options) + set_viewport_size(driver, 1000, 1000) + driver.get('https://aninternettroll.github.io/mcbeVerifierLeaderboard/') + screenshot = driver.find_element_by_id('table').screenshot('leaderboard.png') + driver.quit() + #transparency time + img = Image.open('leaderboard.png') + img = img.convert("RGB") + pallette = Image.open("palette.png") + pallette = pallette.convert("P") + img = img.quantize(colors=256, method=3, kmeans=0, palette=pallette) + img = img.convert("RGBA") + datas = img.getdata() + + newData = [] + for item in datas: + if item[0] == 255 and item[1] == 255 and item[2] == 255: + newData.append((255, 255, 255, 0)) + else: + newData.append(item) + + img.putdata(newData) + """ + img = img.filter(ImageFilter.SHARPEN) + img = img.filter(ImageFilter.SHARPEN) + img = img.filter(ImageFilter.SHARPEN) + """ + #height, width = img.size + #img = img.resize((height*10,width*10), resample=Image.BOX) + img.save("leaderboard.png", "PNG") + class Utils(commands.Cog): def __init__(self, bot): @@ -48,10 +86,12 @@ class Utils(commands.Cog): @commands.cooldown(1, 25, commands.BucketType.guild) @commands.command() async def findseed(self, ctx): - if ctx.message.channel.id != 684787316489060422: + """Test yout luck""" + if ctx.message.channel.id != int(self.bot.config[str(ctx.message.guild.id)]["bot_channel"]): await ctx.message.delete() + ctx.command.reset_cooldown(ctx) return - + # Don't ask rigged_findseed = { 280428276810383370: 12, # Thomas's User ID @@ -67,13 +107,13 @@ class Utils(commands.Cog): randomness = randint(1, 10) if randomness <= 1: totalEyes += 1 - - await ctx.send(f"{ctx.message.author.display_name} -> your seed is a {totalEyes} eye") + await ctx.send(f"{discord.utils.escape_mentions(ctx.message.author.display_name)} -> your seed is a {totalEyes} eye") @findseed.error async def findseed_handler(self,ctx,error): if isinstance(error, commands.CommandOnCooldown): - if ctx.message.channel.id != 684787316489060422: + if ctx.message.channel.id != int(self.bot.config[str(ctx.message.guild.id)]["bot_channel"]): + ctx.command.reset_cooldown(ctx) await ctx.message.delete() return else: @@ -82,7 +122,8 @@ class Utils(commands.Cog): @commands.command() async def findsleep(self, ctx): - if ctx.message.channel.id != 684787316489060422: + """Test your sleep""" + if ctx.message.channel.id != int(self.bot.config[str(ctx.message.guild.id)]["bot_channel"]): await ctx.message.delete() return @@ -98,78 +139,61 @@ class Utils(commands.Cog): "waaakeee uuuppp!", "are they dead or asleep? I can't tell.", "wake up, muffin head", - "psst... coffeeee \:D" + "psst... coffeeee \\:D" ] - - # Set up initial message - msg = f"{ctx.message.author.display_name} -> " # Optional TODO: Create non-normal distribution sleepHrs = randint(0, 24) - # Add sleepHrs with bonus grammar check :D - if sleepHrs == 1: - msg += f"your sleep is {sleepHrs} hour long " - else: - msg += f"your sleep is {sleepHrs} hours long " - # Add extra comment based on number of sleepHrs if sleepHrs == 0: - msg += "- nice try \:D" + await ctx.send(f"{ctx.message.author.display_name} -> your sleep is 0 hours long - nice try \:D") elif sleepHrs <= 5: - msg += f"- {lessSleepMsg[randint(0, len(lessSleepMsg) - 1)]}" - elif sleepHrs >= 10: - msg += f"- {moreSleepMsg[randint(0, len(moreSleepMsg) - 1)]}" - - await ctx.send(msg) + if sleepHrs == 1: + s = '' + else: + s = 's' + await ctx.send(f"{ctx.message.author.display_name} -> your sleep is {sleepHrs} hour{s} long - {lessSleepMsg[randint(0, len(lessSleepMsg) - 1)]}") + else: + await ctx.send(f"{ctx.message.author.display_name} -> your sleep is {sleepHrs} hours long - {moreSleepMsg[randint(0, len(moreSleepMsg) - 1)]}") + + @commands.Cog.listener() + async def on_member_join(self, member): + def check(msg): + return msg.author == member and msg.type != discord.MessageType.new_member + msg = await self.bot.wait_for("message", check=check, timeout=300) + await msg.channel.send("<:PeepoPog:732172337956257872>") + @commands.Cog.listener() async def on_message(self, message): - if message.channel.id != 589110766578434078: + if not message.guild: + return + if message.channel.id != int(self.bot.config[str(message.guild.id)]["fair_channel"]): return if message.author.bot: return badWords = ["fair", "f a i r", "ⓕⓐⓘⓡ", "ⓕ ⓐ ⓘ ⓡ"] count = 0 - + coolKids = [ ['Cameron', self.bot.get_user(468262902969663488), datetime.date(2020, 10, 8)], ['Indy', self.bot.get_user(274923326890311691), datetime.date(2020, 9, 10)], ['Kai', self.bot.get_user(199070670221475842), datetime.date(2020, 11, 20)], ['Luca', self.bot.get_user(99457716614885376), datetime.date(2020, 11, 5)], ['Max', self.bot.get_user(543958509243596800), datetime.date(2020, 11, 10)], + ['Mistaken', self.bot.get_user(264121998173536256), datetime.date(2020, 7, 6)], ['Murray', self.bot.get_user(400344183333847060), datetime.date(2020, 11, 10)], + ['RKREE', self.bot.get_user(395872198323077121), datetime.date(2020, 11, 5)], # idk if she goes by her irl name but I'm sticking with it for the sake of uniformity # also idk how to pronounce prakxo - ['Samantha', self.bot.get_user(226312219787264000), datetime.date(2020, 6, 25)], + ['Samantha', self.bot.get_user(226312219787264000), datetime.date(2020, 6, 24)], ['Scott', self.bot.get_user(223937483774230528), datetime.date(2020, 6, 23)], + ['Sky', self.bot.get_user(329538915805691905), datetime.date(2020, 6, 24)], ['Thomas', self.bot.get_user(280428276810383370), datetime.date(2020, 9, 29)] ] - - - # Luca plz dont remove the bottom code (just incase the new code doesnt work, - # and also for me to laugh at how bad my code is) - - # brb while I write ugly and inefficient code in my - # conquest to make Steve the Bot bloated and unworkable - - #if datetime.date.today() == datetime.date(2020, 6, 23): - # await scott.send('Happy Birthday Scott. You\'re a boomer now! :mango:') - #elif datetime.date.today() == datetime.date(2020, 6, 25): - # await samantha.send('Happy Birthday Prakxo. You\'re a boomer now! :mango:') - #elif datetime.date.today() == datetime.date(2020, 5, 28): - # await thomas.send('Testy Test :mango:') - #elif datetime.date.today() == datetime.date(2020, 9, 29): - # await thomas.send('Now you know how the others felt :mango:') - #elif datetime.date.today() == datetime.date(2020, 10, 8): - # await cameron.send('Happy Birthday Cameron. You\'re a boomer now! :mango:') - #elif datetime.date.today() == datetime.date(2020, 11, 10): - # await murray.send('Happy Birthday Murray. You\'re a boomer now! :mango:') - #elif datetime.date.today() == datetime.date(2020, 9, 10): - # await indy.send('Happy Birthday Indy. You\'re a boomer now! :mango:) - - # Ignore the above message. I got sick and tired of looking at trash code - + + for coolKid in coolKids: if datetime.date.today() == coolKid[2]: try: @@ -178,7 +202,7 @@ class Utils(commands.Cog): self.tries = 1 except: self.tries +=1 - + for word in badWords: if word in message.content.lower(): count += 1; @@ -188,6 +212,7 @@ class Utils(commands.Cog): @commands.cooldown(1, 60, commands.BucketType.member) @commands.command() async def report(self, ctx, *, message=None): + """Send a message to the super mods about anything""" if ctx.message.guild != None: await ctx.message.delete() if message == None: @@ -198,45 +223,10 @@ class Utils(commands.Cog): @commands.cooldown(1, 20, commands.BucketType.member) @commands.command() async def leaderboard(self, ctx): + """Leaderboard of the people that matter""" async with ctx.typing(): - DRIVER = '/usr/lib/chromium-browser/chromedriver' - chrome_options = Options() - chrome_options.add_argument("--disable-dev-shm-usage") - chrome_options.add_argument("--headless") - chrome_options.add_argument("--no-sandbox") - chrome_options.add_argument("--disable-gpu") - #chrome_options.binary_location = "" - driver = webdriver.Chrome(DRIVER, chrome_options=chrome_options) - set_viewport_size(driver, 1000, 1000) - driver.get('https://aninternettroll.github.io/mcbeVerifierLeaderboard/') - screenshot = driver.find_element_by_id('table').screenshot('leaderboard.png') - driver.quit() - #transparency time - img = Image.open('leaderboard.png') - img = img.convert("RGB") - pallette = Image.open("palette.png") - pallette = pallette.convert("P") - img = img.quantize(colors=256, method=3, kmeans=0, palette=pallette) - img = img.convert("RGBA") - datas = img.getdata() - - newData = [] - for item in datas: - if item[0] == 255 and item[1] == 255 and item[2] == 255: - newData.append((255, 255, 255, 0)) - else: - newData.append(item) - - img.putdata(newData) - """ - img = img.filter(ImageFilter.SHARPEN) - img = img.filter(ImageFilter.SHARPEN) - img = img.filter(ImageFilter.SHARPEN) - """ - #height, width = img.size - #img = img.resize((height*10,width*10), resample=Image.BOX) - img.save("leaderboard.png", "PNG") - + lbFunc = functools.partial(save_leaderboard) + await self.bot.loop.run_in_executor(None, lbFunc) await ctx.send(file=discord.File("leaderboard.png")) @@ -245,72 +235,29 @@ class Utils(commands.Cog): if isinstance(error, commands.CommandOnCooldown): #return await ctx.send(f"{ctx.message.author.display_name}, you have to wait {round(error.retry_after, 2)} seconds before using this again.") - - # Why? Because I can. lel - - # celeste guyToday at 6:13 PM - # @Mango Man that's not how it works - - # Mango ManToday at 6:13 PM - # it looks fine in lightmode - # wait whats not how what works - - # celeste guyToday at 6:13 PM - # the command - - # Mango ManToday at 6:13 PM - # o - # how does it work - - # celeste guyToday at 6:14 PM - # Like for a start, nothing is defined - # use ctx D: - - # Mango ManToday at 6:14 PM - # Do I need to though? - - # celeste guyToday at 6:14 PM - # ctx.guild.members() or something - # Yes, server is not a thing - # 2nd, mention is not used like that - # You still have to send a message - # And mention in the message - - # Mango ManToday at 6:14 PM - # o - - # celeste guyToday at 6:15 PM - # 3rd, don't forget to import choice from random - - # Mango ManToday at 6:15 PM - # this is why you dont steal code from github - # I actually feel embarrased over forgetting to import random - - # celeste guyToday at 6:15 PM - # 4th, add ctx in the arguments list, or you'll get an error like "function takes 1 argument but 2 were given" - # And you will use it to send the message and get the server - # Also forgetting the import is the least embarrassing thing - # Since I did remove it - # And replaced with import randint from random - + + @commands.cooldown(1, 60, commands.BucketType.guild) @commands.command() async def someone(self, ctx): - blacklist = [536071288859656193] - if ctx.channel.id != 589110766578434078: - if ctx.author.id == 395872198323077121: - await ctx.send("grape is a bitch") - elif ctx.author.id == 521153476714299402: - await ctx.send("ZMG is smooth brain") - elif ctx.author.id == 199070670221475842: - await ctx.send(f"fuck you {ctx.message.author.mention}") - elif ctx.author.id in blacklist: - await ctx.send("not even bothering with a message for you. You're just an edgy sheep") - else: - await ctx.send(choice(ctx.guild.members).mention) + """Discord's mistake""" + if ctx.channel.id != int(self.bot.config[str(ctx.message.guild.id)]["fair_channel"]): + await ctx.send(choice(ctx.guild.members).mention) @commands.command() async def roll(self, ctx, pool): + """Toll the dice""" await ctx.send(f"You rolled a {randint(0, int(pool))}") + @commands.command(aliases=['commands', 'allcommands']) + async def listcommands(self, ctx): + """List all custom commands""" + with open('custom_commands.json', 'r') as f: + commands = json.load(f) + output = '```List of custom commands:\n' + for key in commands: + output += f'{key}, ' + output += '```' + await ctx.send(output) + def setup(bot): bot.add_cog(Utils(bot)) diff --git a/custom_commands.json b/custom_commands.json index c87825f..a651cc8 100755 --- a/custom_commands.json +++ b/custom_commands.json @@ -1,5 +1,4 @@ { - "!sr.c": "https://www.speedrun.com/mcbe", "/Troll": "is Super at his job", "/src": "https://www.speedrun.com/mcbe", "!Walk-On-through-the-Wind": "Walk On through the rain", @@ -20,7 +19,6 @@ "/swipe": "not an alt", "!HereWeGo": "10 in a Row!", "/Make": "Troll Supermod", - "!h": "<@!199070670221475842> no swearing in this christian discord server", "/boards": "https://www.speedrun.com/mcbe", "/ban": "shut up", "/pending": "this annoys me", @@ -30,8 +28,16 @@ "!lenny": "( \u0361\u00b0 \u035c\u0296 \u0361\u00b0)", "!hwg": "10 iar", "!GlasgowRangers": "You Let Your Club Die!", - "!murray": "the irishest of the Irish", "!e\ud83c\udd71\ufe0fic": "HEY GUYS ITS SHIGAME45", "!testy": "test", - "!toxicitypass": "```TOXICITY PASS\n\nTHE HOLDER OF THIS PASS HAS PERMISSION FROM THE HEAVENS ABOVE TO DO AND SAY WHATEVER THE HELL THEY WANT\nTHEY ARE ALLOWED TO BE AS TOXIC AS THEY WILL WITHOUT REPERCUSSIONS\n\nPASS OWNER: Duck W aka Weexy aka Indy Kambeitz```" + "!toxicitypass": "```TOXICITY PASS\n\nTHE HOLDER OF THIS PASS HAS PERMISSION FROM THE HEAVENS ABOVE TO DO AND SAY WHATEVER THE HELL THEY WANT\nTHEY ARE ALLOWED TO BE AS TOXIC AS THEY WILL WITHOUT REPERCUSSIONS\n\nPASS OWNER: Duck W aka Weexy aka Indy Kambeitz```", + "!welcome": "Welcome! <:mango:715738087627685908>", + "!learntoread": "When two players are tied on the leaderboards **ONLY ONE FUCKER WILL BE VISIBLE**", + "!mobiledupe": "1) place item to dupe in the crafting grid\n2) keep pressed until the green bar thing appears\nclick any \"Impossible\" craft (red ones)\n3)press the item to dupe (that is in your hotbar now -> it should have moved there since you pressed the impossible craft)\n4)your item should be duped", + "!source": "https://github.com/AnInternetTroll/mcbeDiscordBot", + "!sr.c": "https://www.speedrun.com/mcbe", + "!lbsite": "https://aninternettroll.github.io/mcbeVerifierLeaderboard/index.html", + "!leaderboardsite": "https://aninternettroll.github.io/mcbeVerifierLeaderboard/index.html", + "!stop_crying": "Mods are human, we have lives. If your run isn't verified within 0.1 seconds of you submitting it then please don't Dm us asking us to verify your run. Speedrun.com gives you up to 3 weeks as an estimated time so if after 3 weeks have passed and your run hasn't been verified, then shoot one of us a Dm. If you constantly Dm us and hassle mods, you will get 3 warnings before you become muted and if you still continue, punishments will escalate like other breachments of the rules.", + "!staff": "https://media.discordapp.net/attachments/709672550707363931/721226547817873519/tenor.gif" }
\ No newline at end of file @@ -29,9 +29,9 @@ def run_bot(): bot.run() if __name__ == "__main__": - + init_colorama(autoreset=True) setup_logging() - + run_bot() @@ -1,8 +1,28 @@ # Minecraft Bedrock Discord Bot ## How to -Make a file called `config.py` and add a `token="DiscordToken"` variable in there. +Make a file called `config.json` and add +```json +{ + "token": "your_bot_token" +} +``` + Launch the bot with `python3 main.py` and you're ready to go. Unless dependencies. Dependencies are google cloud and discord. -This bot was built as a fork of [celesteBot](https://github.com/CelesteClassic/celestebot), so a lot of code is recycled. +Install the dependencies with `python -m pip install -r requirements.txt` + +A few "dangerous" commands such as `!purge` are restriced to `bot_masters`, to add a bot master add it to `config.json`. Example: +```json +{ + "token": "your_bot_token", + "bot_masters": <users_discord_id> +} +``` +You can also use lists, for example: `"bot_masters": [280428276810383370, 99457716614885376]` + +A user added as a botmaster will be able to edit the config via discord with the command `!setvar <var_name> <var_value>` +`!setvar` also supports lists which can be added like so: `!setvar <var_name> [<index 0>, <index 1>]` + +This bot was built as a fork of [celesteBot](https://github.com/CelesteClassic/celestebot), so a lot of code is recycled. Feel free to make a pull request or use the code here. |