From 0baa36497519711953603db640bf099db19af826 Mon Sep 17 00:00:00 2001 From: Mango0x45 Date: Sat, 12 Sep 2020 15:13:29 +0200 Subject: Black is neat --- cogs/admin.py | 8 +- cogs/general.py | 1359 ++++++++++++++++++++++++++++------------------------- cogs/logs.py | 97 ++-- cogs/player.py | 719 +++++++++++++++------------- cogs/src.py | 711 ++++++++++++++-------------- cogs/trans.py | 104 ++-- cogs/twitter.py | 81 ++-- cogs/utils.py | 2 - cogs/webserver.py | 87 ++-- 9 files changed, 1667 insertions(+), 1501 deletions(-) (limited to 'cogs') diff --git a/cogs/admin.py b/cogs/admin.py index 176c295..a966334 100755 --- a/cogs/admin.py +++ b/cogs/admin.py @@ -130,7 +130,7 @@ class Admin(commands.Cog): after=None, around=None, oldest_first=False, - bulk=True + bulk=True, ) @commands.command() @@ -141,7 +141,7 @@ class Admin(commands.Cog): members: commands.Greedy[discord.Member] = False, mute_minutes: int = 0, *, - reason: str = "absolutely no reason" + reason: str = "absolutely no reason", ): """Mass mute members with an optional mute_minutes parameter to time it""" @@ -297,7 +297,9 @@ class Admin(commands.Cog): async def delvar(self, ctx, key): """Deletes a config variable, be careful""" with open("config.json", "w") as f: - await ctx.send(f"Removed {self.bot.config[str(ctx.message.guild.id)].pop(key)}") + await ctx.send( + f"Removed {self.bot.config[str(ctx.message.guild.id)].pop(key)}" + ) json.dump(self.bot.config, f, indent=4) @commands.command() diff --git a/cogs/general.py b/cogs/general.py index d4813db..0ad808a 100755 --- a/cogs/general.py +++ b/cogs/general.py @@ -12,607 +12,657 @@ from discord.ext import commands 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) +sys.path.insert(0, parent_dir) import bot def dump(obj): - output = "" - for attr in dir(obj): - output += "\nobj.%s = %r" % (attr, getattr(obj, attr)) - print("obj.%s = %r" % (attr, getattr(obj, attr))) - return output + output = "" + for attr in dir(obj): + output += "\nobj.%s = %r" % (attr, getattr(obj, attr)) + print("obj.%s = %r" % (attr, getattr(obj, attr))) + 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)] + 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 = ", ".join(prefixes) - - 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 - - output = "" - for i in user.roles: - output += i.mention + " " - - if user.color.value == 0: - color = 16777210 - else: - color = user.color - - 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=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) - embed.add_field(name="Created account on", value=user.created_at.date(), inline=True) - embed.add_field(name="Roles", value=output, inline=True) - embed.set_footer(text=f"ID: {user.id}") - await ctx.send(embed=embed) - #os.remove("temp.webp") - #os.remove("temp.png") - - @commands.command() - async def coop(self, ctx, *, user: discord.Member=None): - """Get the coop gang role""" - if not user: - user = ctx.message.author - else: - user = self.bot.get_user(int(user)) - - 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) - await ctx.send('You have left coop gang') - else: - 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 """ - """ + 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 = ", ".join(prefixes) + + 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 + + output = "" + for i in user.roles: + output += i.mention + " " + + if user.color.value == 0: + color = 16777210 + else: + color = user.color + + 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=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) + embed.add_field( + name="Created account on", value=user.created_at.date(), inline=True + ) + embed.add_field(name="Roles", value=output, inline=True) + embed.set_footer(text=f"ID: {user.id}") + await ctx.send(embed=embed) + # os.remove("temp.webp") + # os.remove("temp.png") + + @commands.command() + async def coop(self, ctx, *, user: discord.Member = None): + """Get the coop gang role""" + if not user: + user = ctx.message.author + else: + user = self.bot.get_user(int(user)) + + 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) + await ctx.send("You have left coop gang") + else: + 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 """ + """ listRequest = requests.get("https://wandbox.org/api/list.json") compilerList = json.loads(listRequest.text) @@ -621,63 +671,72 @@ class General(commands.Cog): 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)}```") + 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)) + bot.add_cog(General(bot)) diff --git a/cogs/logs.py b/cogs/logs.py index 21516be..9912d63 100755 --- a/cogs/logs.py +++ b/cogs/logs.py @@ -3,59 +3,54 @@ from discord.ext import commands class Logs(commands.Cog): - def __init__(self, bot): - self.bot = bot + def __init__(self, bot): + self.bot = bot - @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 - 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=color, - timestamp=message.created_at - ) - embed.add_field( - name='**User**', value=message.author.mention, inline=True) - embed.add_field( - name='**Channel**', value=message.channel.mention, inline=True) - embed.add_field(name='**Message**', value=message.content, inline=False) - await channel.send(embed=embed) + @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 + 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=color, timestamp=message.created_at + ) + embed.add_field(name="**User**", value=message.author.mention, inline=True) + embed.add_field(name="**Channel**", value=message.channel.mention, inline=True) + embed.add_field(name="**Message**", value=message.content, inline=False) + await channel.send(embed=embed) - @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(int(self.bot.config[str(before.guild.id)]["logs_channel"])) - if before.author.color.value == 0: - color = 16777210 - else: - color = before.author.color - embed = discord.Embed( - title='**Edited Message**', - color=color, - timestamp=after.edited_at - ) - embed.add_field( - name='**User**', value=before.author.mention, inline=True) - embed.add_field( - name='**Channel**', value=before.channel.mention, inline=True) - embed.add_field(name='**Original Message**', - value=before.content, inline=False) - embed.add_field(name='**New Message**', value=after.content, inline=False) - await channel.send(embed=embed) + @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( + int(self.bot.config[str(before.guild.id)]["logs_channel"]) + ) + if before.author.color.value == 0: + color = 16777210 + else: + color = before.author.color + embed = discord.Embed( + title="**Edited Message**", color=color, timestamp=after.edited_at + ) + embed.add_field(name="**User**", value=before.author.mention, inline=True) + embed.add_field(name="**Channel**", value=before.channel.mention, inline=True) + embed.add_field(name="**Original Message**", value=before.content, inline=False) + embed.add_field(name="**New Message**", value=after.content, inline=False) + await channel.send(embed=embed) def setup(bot): - bot.add_cog(Logs(bot)) + bot.add_cog(Logs(bot)) diff --git a/cogs/player.py b/cogs/player.py index 605d5ef..f21992d 100755 --- a/cogs/player.py +++ b/cogs/player.py @@ -31,90 +31,103 @@ from discord.ext import commands, tasks from youtube_dl import YoutubeDL ytdlopts = { - 'format': 'bestaudio/best', - 'outtmpl': 'downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s', - 'restrictfilenames': True, - 'noplaylist': True, - 'nocheckcertificate': True, - 'ignoreerrors': False, - 'logtostderr': False, - 'quiet': True, - 'no_warnings': True, - 'default_search': 'auto', - 'source_address': '0.0.0.0' # ipv6 addresses cause issues sometimes + "format": "bestaudio/best", + "outtmpl": "downloads/%(extractor)s-%(id)s-%(title)s.%(ext)s", + "restrictfilenames": True, + "noplaylist": True, + "nocheckcertificate": True, + "ignoreerrors": False, + "logtostderr": False, + "quiet": True, + "no_warnings": True, + "default_search": "auto", + "source_address": "0.0.0.0", # ipv6 addresses cause issues sometimes } ffmpegopts = { - 'before_options': '-nostdin', - 'options': '-vn -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5' + "before_options": "-nostdin", + "options": "-vn -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5", } ytdl = YoutubeDL(ytdlopts) class VoiceConnectionError(commands.CommandError): - """Custom Exception class for connection errors.""" + """Custom Exception class for connection errors.""" class InvalidVoiceChannel(VoiceConnectionError): - """Exception for cases of invalid Voice Channels.""" + """Exception for cases of invalid Voice Channels.""" class YTDLSource(discord.PCMVolumeTransformer): + def __init__(self, source, *, data, requester): + super().__init__(source) + self.requester = requester - def __init__(self, source, *, data, requester): - super().__init__(source) - self.requester = requester + self.title = data.get("title") + self.web_url = data.get("webpage_url") - self.title = data.get('title') - 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 - # 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. + 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 create_source(cls, ctx, search: str, *, loop, download=True): - loop = loop or asyncio.get_event_loop() - - 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] - - 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. + return self.__getattribute__(item) + + @classmethod + async def create_source(cls, ctx, search: str, *, loop, download=True): + loop = loop or asyncio.get_event_loop() + + 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] + + 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'] + loop = loop or asyncio.get_event_loop() + requester = data["requester"] - to_run = partial(ytdl.extract_info, url=data['webpage_url']) - data = await loop.run_in_executor(None, to_run) + to_run = partial(ytdl.extract_info, url=data["webpage_url"]) + data = await loop.run_in_executor(None, to_run) - return cls(discord.FFmpegPCMAudio(data['url'], **ffmpegopts), data=data, requester=requester) + 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. + """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. @@ -122,134 +135,159 @@ class MusicPlayer: 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)) + __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 = 0.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.removedownloads.start() - self.players = {} - - async def cleanup(self, guild): - try: - await guild.voice_client.disconnect() - except AttributeError: - 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='connect', aliases=['join']) - async def connect_(self, ctx, *, channel: discord.VoiceChannel=None): - """Connect to voice. + """Music related commands.""" + + __slots__ = ("bot", "players") + + def __init__(self, bot): + self.bot = bot + # self.removedownloads.start() + self.players = {} + + async def cleanup(self, guild): + try: + await guild.voice_client.disconnect() + except AttributeError: + 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="connect", aliases=["join"]) + async def connect_(self, ctx, *, channel: discord.VoiceChannel = None): + """Connect to voice. Parameters ------------ @@ -259,32 +297,36 @@ class Player(commands.Cog): 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. + 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. @@ -294,149 +336,166 @@ class Player(commands.Cog): 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_) - - player = self.get_player(ctx) + await ctx.trigger_typing() - # 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) + vc = ctx.voice_client + + if not vc: + await ctx.invoke(self.connect_) - await player.queue.put(source) - - @commands.command(name='pause') - async def pause_(self, ctx): - """Pause the currently playing song.""" - vc = ctx.voice_client - - 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 - - player.np = await ctx.send(f'**Now Playing:** `{vc.source.title}` ' - f'requested by `{vc.source.requester}`') - - @commands.command(name='volume', aliases=['vol']) - async def change_volume(self, ctx, *, vol: float): - """Change the player volume. + player = self.get_player(ctx) + + # 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) + + await player.queue.put(source) + + @commands.command(name="pause") + async def pause_(self, ctx): + """Pause the currently playing song.""" + vc = ctx.voice_client + + 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 + + player.np = await ctx.send( + f"**Now Playing:** `{vc.source.title}` " + f"requested by `{vc.source.requester}`" + ) + + @commands.command(name="volume", aliases=["vol"]) + async def change_volume(self, ctx, *, vol: float): + """Change the player volume. 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 + 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) + if not vc or not vc.is_connected(): + return await ctx.send( + "I am not currently connected to voice!", delete_after=20 + ) - if not 0 < vol < 101: - return await ctx.send('Please enter a value between 1 and 100.') + if not 0 < vol < 101: + return await ctx.send("Please enter a value between 1 and 100.") - player = self.get_player(ctx) + player = self.get_player(ctx) - if vc.source: - vc.source.volume = vol / 100 + if vc.source: + vc.source.volume = vol / 100 - player.volume = vol / 100 - await ctx.send(f'**`{ctx.author}`**: Set the volume to **{vol}%**') + player.volume = vol / 100 + await ctx.send(f"**`{ctx.author}`**: Set the volume to **{vol}%**") - @commands.command(name='stop') - async def stop_(self, ctx): - """Stop the currently playing song and destroy the player. + @commands.command(name="stop") + async def stop_(self, ctx): + """Stop the currently playing song and destroy the player. !Warning! This will destroy the player assigned to your guild, also deleting any queued songs and settings. """ - vc = ctx.voice_client + 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 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 removedownloads(self): - for p in Path("./downloads/").glob("*"): - p.unlink() + await self.cleanup(ctx.guild) + @tasks.loop(hours=10.0) + async def removedownloads(self): + for p in Path("./downloads/").glob("*"): + p.unlink() -def setup(bot): - bot.add_cog(Player(bot)) +def setup(bot): + bot.add_cog(Player(bot)) diff --git a/cogs/src.py b/cogs/src.py index c179a51..4c1f82e 100755 --- a/cogs/src.py +++ b/cogs/src.py @@ -11,371 +11,388 @@ from discord.utils import get class SubmittedRun: - def __init__(self, game, _id, category, video, players, duration, _type): - self.game = game - self._id = _id - self.category = category - self.video = video - self.players = players - self.duration = duration - self._type = _type - self.link = f'https://www.speedrun.com/{game}/run/{_id}' + def __init__(self, game, _id, category, video, players, duration, _type): + self.game = game + self._id = _id + self.category = category + self.video = video + self.players = players + self.duration = duration + self._type = _type + self.link = f"https://www.speedrun.com/{game}/run/{_id}" async def rejectRun(self, apiKey, ctx, run, reason): - await ctx.message.delete() - run = run.split('/')[-1] - reject = {"status": {"status": "rejected", "reason": reason}} - r = requests.put(f"https://www.speedrun.com/api/v1/runs/{run}/status", - headers={ - "X-API-Key": apiKey, - "Accept": "application/json", - "User-Agent": "mcbeDiscordBot/1.0" - }, - data=json.dumps(reject)) - if r.status_code == 200 or r.status_code == 204: - await ctx.send(f'Run rejected succesfully for `{reason}`') - else: - await ctx.send("Something went wrong") - await ctx.message.author.send( - f"```json\n{json.dumps(json.loads(r.text),indent=4)}```") + await ctx.message.delete() + run = run.split("/")[-1] + reject = {"status": {"status": "rejected", "reason": reason}} + r = requests.put( + f"https://www.speedrun.com/api/v1/runs/{run}/status", + headers={ + "X-API-Key": apiKey, + "Accept": "application/json", + "User-Agent": "mcbeDiscordBot/1.0", + }, + data=json.dumps(reject), + ) + if r.status_code == 200 or r.status_code == 204: + await ctx.send(f"Run rejected succesfully for `{reason}`") + else: + await ctx.send("Something went wrong") + await ctx.message.author.send( + f"```json\n{json.dumps(json.loads(r.text),indent=4)}```" + ) async def approveRun(self, apiKey, ctx, run, reason=None): - await ctx.message.delete() - run = run.split('/')[-1] - if reason == None: - approve = {"status": {"status": "verified"}} - else: - approve = {"status": {"status": "verified", "reason": reason}} - r = requests.put(f"https://www.speedrun.com/api/v1/runs/{run}/status", - headers={ - "X-API-Key": apiKey, - "Accept": "application/json", - "User-Agent": "mcbeDiscordBot/1.0" - }, - data=json.dumps(approve)) - if r.status_code == 200 or r.status_code == 204: - await ctx.send("Run approved succesfully") - else: - await ctx.send("Something went wrong") - await ctx.message.author.send( - f"```json\n{json.dumps(json.loads(r.text),indent=4)}```") + await ctx.message.delete() + run = run.split("/")[-1] + if reason == None: + approve = {"status": {"status": "verified"}} + else: + approve = {"status": {"status": "verified", "reason": reason}} + r = requests.put( + f"https://www.speedrun.com/api/v1/runs/{run}/status", + headers={ + "X-API-Key": apiKey, + "Accept": "application/json", + "User-Agent": "mcbeDiscordBot/1.0", + }, + data=json.dumps(approve), + ) + if r.status_code == 200 or r.status_code == 204: + await ctx.send("Run approved succesfully") + else: + await ctx.send("Something went wrong") + await ctx.message.author.send( + f"```json\n{json.dumps(json.loads(r.text),indent=4)}```" + ) async def deleteRun(self, apiKey, ctx, run): - await ctx.message.delete() - run = run.split('/')[-1] - r = requests.delete(f"https://www.speedrun.com/api/v1/runs/{run}", - headers={ - "X-API-Key": apiKey, - "Accept": "application/json", - "User-Agent": "mcbeDiscordBot/1.0" - }) - if r.status_code == 200 or r.status_code == 204: - await ctx.send("Run deleted succesfully") - else: - await ctx.send("Something went wrong") - await ctx.message.author.send( - f"```json\n{json.dumps(json.loads(r.text),indent=4)}```") + await ctx.message.delete() + run = run.split("/")[-1] + r = requests.delete( + f"https://www.speedrun.com/api/v1/runs/{run}", + headers={ + "X-API-Key": apiKey, + "Accept": "application/json", + "User-Agent": "mcbeDiscordBot/1.0", + }, + ) + if r.status_code == 200 or r.status_code == 204: + await ctx.send("Run deleted succesfully") + else: + await ctx.send("Something went wrong") + await ctx.message.author.send( + f"```json\n{json.dumps(json.loads(r.text),indent=4)}```" + ) async def pendingRuns(self, ctx): - def banned_player_coop(run): - for player in run.players: - if player in self.bot.runs_blacklist["players"]: - return True - return False - - def duplicate_run(run): - for pending_run in pending_runs: - if run._id != pending_run._id and run.category == pending_run.category and run.video == pending_run.video and run.duration == pending_run.duration: - return True - return False - - def get_player_name(player): - if player["rel"] == 'user': - return player["names"]["international"] - else: - return player["name"] - - mcbe_runs = 0 - mcbeil_runs = 0 - mcbece_runs = 0 - pending_runs = [] - runs_to_reject = [] - game_ids = ['yd4ovvg1', 'v1po7r76'] # [mcbe, mcbece] - head = {"Accept": "application/json", "User-Agent": "mcbeDiscordBot/1.0"} - - for game in game_ids: - runs_request = requests.get( - f'https://www.speedrun.com/api/v1/runs?game={game}&status=new&max=200&embed=category,players,level&orderby=submitted', - headers=head) - runs = json.loads(runs_request.text) - - for run in runs["data"]: - _id = run["id"] - duration = timedelta(seconds=run["times"]["realtime_t"]) - - if run["videos"] != None: - try: - video = run["videos"]["links"][0]["uri"] - except KeyError: - video = run["videos"]["text"] - else: - video = None - - # Get the category name for each run, while specifiying if its a full game, il, or cat ext run - if run["level"]["data"] != []: - category = run["level"]["data"]["name"] - if game == 'yd4ovvg1': - _type = 'Individual Level' - else: - category = run["category"]["data"]["name"] - if game == 'yd4ovvg1': - _type = 'Full Game Run' - - if game == 'v1po7r76': - _type = 'Category Extension' - - # Set players to a string if solo, or a list if coop - if len(run["players"]["data"]) == 1: - players = get_player_name(run["players"]["data"][0]) - else: - players = list( - map(lambda player: get_player_name(player), - run["players"]["data"])) - - pending_run = SubmittedRun(game, _id, category, video, players, - duration, _type) - pending_runs.append(pending_run) - - for run in pending_runs: - # Reject run if video is blacklisted - if run.video.split('/')[-1].split( - '=')[-1] in self.bot.runs_blacklist["videos"]: - runs_to_reject.append( - [run, 'Detected as spam by our automatic filter.']) - - # Reject run if player is banned (solo runs) - elif type( - run.players - ) == str and run.players in self.bot.runs_blacklist["players"]: - runs_to_reject.append([ - run, - f'Detected as a banned player ({run.players}) run by our automatic filter.' - ]) - - # Reject run if player is banned (coop runs) - elif banned_player_coop(run) == True: - runs_to_reject.append([ - run, - f'Detected a banned player in the list of runners ({run.players}) by our automatic filter.' - ]) - - # Reject run if duplicate submission - elif duplicate_run(run) == True: - runs_to_reject.append([ - run, - 'Detected as a duplicate submission by our automatic filter.' - ]) - pending_runs.remove(run) - - else: - if run._type == 'Full Game Run': - mcbe_runs += 1 - elif run._type == 'Individual Level': - mcbeil_runs += 1 - else: - mcbece_runs += 1 - - # In the case of coop, change the player names from a list to a string for prettier output - if type(run.players) == list: - run.players = ', '.join(map(str, run.players)) - - embed = discord.Embed( - title=run._type, - url=run.link, - description= - f"{run.category} in `{str(run.duration).replace('000','')}` by **{run.players}**", - color=0x9400D3) - await self.bot.get_channel( - int(self.bot.config[str( - ctx.message.guild.id)]["pending_channel"]) - ).send(embed=embed) - - 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}\nTotal Runs: {mcbe_runs+mcbeil_runs+mcbece_runs}", - color=0x000000) - await self.bot.get_channel( - int(self.bot.config[str(ctx.message.guild.id)]["pending_channel"]) - ).send(embed=embed_stats) - - for run in runs_to_reject: - try: - reject = {"status": {"status": "rejected", "reason": run[1]}} - r = requests.put( - f"https://www.speedrun.com/api/v1/runs/{run[0]._id}/status", - headers={ - "X-API-Key": self.bot.config["api_key"], - "Accept": "application/json", - "User-Agent": "mcbeDiscordBot/1.0" - }, - data=json.dumps(reject)) - if r.status_code == 200 or r.status_code == 204: - await ctx.send( - f'Run rejected succesfully for `{run[1]}`\nLink: {run[0].link}' - ) - else: - await ctx.send("Something went wrong") - await ctx.message.author.send( - f"```json\n{json.dumps(json.loads(r.text),indent=4)}```") - except: - continue + def banned_player_coop(run): + for player in run.players: + if player in self.bot.runs_blacklist["players"]: + return True + return False + + def duplicate_run(run): + for pending_run in pending_runs: + if ( + run._id != pending_run._id + and run.category == pending_run.category + and run.video == pending_run.video + and run.duration == pending_run.duration + ): + return True + return False + + def get_player_name(player): + if player["rel"] == "user": + return player["names"]["international"] + else: + return player["name"] + + mcbe_runs = 0 + mcbeil_runs = 0 + mcbece_runs = 0 + pending_runs = [] + runs_to_reject = [] + game_ids = ["yd4ovvg1", "v1po7r76"] # [mcbe, mcbece] + head = {"Accept": "application/json", "User-Agent": "mcbeDiscordBot/1.0"} + + for game in game_ids: + runs_request = requests.get( + f"https://www.speedrun.com/api/v1/runs?game={game}&status=new&max=200&embed=category,players,level&orderby=submitted", + headers=head, + ) + runs = json.loads(runs_request.text) + + for run in runs["data"]: + _id = run["id"] + duration = timedelta(seconds=run["times"]["realtime_t"]) + + if run["videos"] != None: + try: + video = run["videos"]["links"][0]["uri"] + except KeyError: + video = run["videos"]["text"] + else: + video = None + + # Get the category name for each run, while specifiying if its a full game, il, or cat ext run + if run["level"]["data"] != []: + category = run["level"]["data"]["name"] + if game == "yd4ovvg1": + _type = "Individual Level" + else: + category = run["category"]["data"]["name"] + if game == "yd4ovvg1": + _type = "Full Game Run" + + if game == "v1po7r76": + _type = "Category Extension" + + # Set players to a string if solo, or a list if coop + if len(run["players"]["data"]) == 1: + players = get_player_name(run["players"]["data"][0]) + else: + players = list( + map(lambda player: get_player_name(player), run["players"]["data"]) + ) + + pending_run = SubmittedRun( + game, _id, category, video, players, duration, _type + ) + pending_runs.append(pending_run) + + for run in pending_runs: + # Reject run if video is blacklisted + if run.video.split("/")[-1].split("=")[-1] in self.bot.runs_blacklist["videos"]: + runs_to_reject.append([run, "Detected as spam by our automatic filter."]) + + # Reject run if player is banned (solo runs) + elif ( + type(run.players) == str + and run.players in self.bot.runs_blacklist["players"] + ): + runs_to_reject.append( + [ + run, + f"Detected as a banned player ({run.players}) run by our automatic filter.", + ] + ) + + # Reject run if player is banned (coop runs) + elif banned_player_coop(run) == True: + runs_to_reject.append( + [ + run, + f"Detected a banned player in the list of runners ({run.players}) by our automatic filter.", + ] + ) + + # Reject run if duplicate submission + elif duplicate_run(run) == True: + runs_to_reject.append( + [run, "Detected as a duplicate submission by our automatic filter."] + ) + pending_runs.remove(run) + + else: + if run._type == "Full Game Run": + mcbe_runs += 1 + elif run._type == "Individual Level": + mcbeil_runs += 1 + else: + mcbece_runs += 1 + + # In the case of coop, change the player names from a list to a string for prettier output + if type(run.players) == list: + run.players = ", ".join(map(str, run.players)) + + embed = discord.Embed( + title=run._type, + url=run.link, + description=f"{run.category} in `{str(run.duration).replace('000','')}` by **{run.players}**", + color=0x9400D3, + ) + await self.bot.get_channel( + int(self.bot.config[str(ctx.message.guild.id)]["pending_channel"]) + ).send(embed=embed) + + 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}\nTotal Runs: {mcbe_runs+mcbeil_runs+mcbece_runs}", + color=0x000000, + ) + await self.bot.get_channel( + int(self.bot.config[str(ctx.message.guild.id)]["pending_channel"]) + ).send(embed=embed_stats) + + for run in runs_to_reject: + try: + reject = {"status": {"status": "rejected", "reason": run[1]}} + r = requests.put( + f"https://www.speedrun.com/api/v1/runs/{run[0]._id}/status", + headers={ + "X-API-Key": self.bot.config["api_key"], + "Accept": "application/json", + "User-Agent": "mcbeDiscordBot/1.0", + }, + data=json.dumps(reject), + ) + if r.status_code == 200 or r.status_code == 204: + await ctx.send( + f"Run rejected succesfully for `{run[1]}`\nLink: {run[0].link}" + ) + else: + await ctx.send("Something went wrong") + await ctx.message.author.send( + f"```json\n{json.dumps(json.loads(r.text),indent=4)}```" + ) + except: + continue async def verifyNew(self, apiKey=None, userID=None): - if apiKey == None: - head = { - "Accept": "application/json", - "User-Agent": "mcbeDiscordBot/1.0" - } - else: - head = { - "X-API-Key": apiKey, - "Accept": "application/json", - "User-Agent": "mcbeDiscordBot/1.0" - } - server = self.bot.get_guild(574267523869179904) - # Troll is mentally challenged I guess ¯\_(ツ)_/¯ - RunneRole = server.get_role(574268937454223361) - WrRole = server.get_role(583622436378116107) - #if userID == None: - # return - #else: - user = self.bot.get_user(int(userID)) - data = json.loads(Path('./api_keys.json').read_text()) - - if str(user.id) in data: - pbs = requests.get( - f"https://www.speedrun.com/api/v1/users/{data[str(user.id)]}/personal-bests", - headers=head) - pbs = json.loads(pbs.text) - else: - r = requests.get('https://www.speedrun.com/api/v1/profile', - headers=head) - # print(r.text) - if r.status_code >= 400: - await user.send(f"```json\n{r.text}```") - return - try: - profile = json.loads(r.text) - except json.decoder.JSONDecodeError: - return - srcUserID = profile["data"]["id"] - with open('api_keys.json', 'w') as file: - data[user.id] = srcUserID - json.dump(data, file, indent=4) - pbs = requests.get(profile["data"]["links"][3]["uri"], headers=head) - pbs = json.loads(pbs.text) - - wrCounter = False - runnerCounter = False - - for i in pbs["data"]: - if i["place"] == 1: - if i["run"]["game"] == "yd4ovvg1" or i["run"]["game"] == "v1po7r76": - if not i["run"]["level"]: - wrCounter = True - if i["run"]["game"] == "yd4ovvg1" or i["run"]["game"] == "v1po7r76": - # I have no shame - runnerCounter = True - - if wrCounter: - await server.get_member(user.id).add_roles(WrRole) - else: - await server.get_member(user.id).remove_roles(WrRole) - if runnerCounter: - await server.get_member(user.id).add_roles(RunneRole) - else: - await server.get_member(user.id).remove_roles(RunneRole) + if apiKey == None: + head = {"Accept": "application/json", "User-Agent": "mcbeDiscordBot/1.0"} + else: + head = { + "X-API-Key": apiKey, + "Accept": "application/json", + "User-Agent": "mcbeDiscordBot/1.0", + } + server = self.bot.get_guild(574267523869179904) + # Troll is mentally challenged I guess ¯\_(ツ)_/¯ + RunneRole = server.get_role(574268937454223361) + WrRole = server.get_role(583622436378116107) + # if userID == None: + # return + # else: + user = self.bot.get_user(int(userID)) + data = json.loads(Path("./api_keys.json").read_text()) + + if str(user.id) in data: + pbs = requests.get( + f"https://www.speedrun.com/api/v1/users/{data[str(user.id)]}/personal-bests", + headers=head, + ) + pbs = json.loads(pbs.text) + else: + r = requests.get("https://www.speedrun.com/api/v1/profile", headers=head) + # print(r.text) + if r.status_code >= 400: + await user.send(f"```json\n{r.text}```") + return + try: + profile = json.loads(r.text) + except json.decoder.JSONDecodeError: + return + srcUserID = profile["data"]["id"] + with open("api_keys.json", "w") as file: + data[user.id] = srcUserID + json.dump(data, file, indent=4) + pbs = requests.get(profile["data"]["links"][3]["uri"], headers=head) + pbs = json.loads(pbs.text) + + wrCounter = False + runnerCounter = False + + for i in pbs["data"]: + if i["place"] == 1: + if i["run"]["game"] == "yd4ovvg1" or i["run"]["game"] == "v1po7r76": + if not i["run"]["level"]: + wrCounter = True + if i["run"]["game"] == "yd4ovvg1" or i["run"]["game"] == "v1po7r76": + # I have no shame + runnerCounter = True + + if wrCounter: + await server.get_member(user.id).add_roles(WrRole) + else: + await server.get_member(user.id).remove_roles(WrRole) + if runnerCounter: + await server.get_member(user.id).add_roles(RunneRole) + else: + await server.get_member(user.id).remove_roles(RunneRole) class Src(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.checker.start() - - async def is_mod(ctx): - return ctx.author.guild_permissions.manage_channels - - @commands.command(description="Posts all pending runs to #pending-runs") - @commands.guild_only() - async def pending(self, ctx): - async with ctx.typing(): - 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") - @commands.check(is_mod) - @commands.guild_only() - async def reject( - self, - ctx, - apiKey, - run, - *, - reason="Rejected using Steve. No additional reason provided"): - if apiKey == None: - apiKey = self.bot.config['api_key'] - await rejectRun(self, apiKey, ctx, run, reason) - - @commands.command(description="Approve runs quickly") - @commands.check(is_mod) - @commands.guild_only() - async def approve( - self, - ctx, - apiKey, - run, - *, - reason="Approved using Steve. No additional reason provided"): - if apiKey == None: - apiKey = self.bot.config['api_key'] - await approveRun(self, apiKey, ctx, run, reason) - - @commands.command(description="Delete runs quickly") - async def delete(self, ctx, apiKey, run): - await deleteRun(self, apiKey, ctx, run) - - @commands.command() - async def verify(self, ctx, apiKey=None, userID=None): - 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 this command again by getting an api key from https://www.speedrun.com/api/auth then do `{ctx.prefix}verify ` 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(): - if userID == None: - userID = ctx.message.author.id - await verifyNew(self, apiKey, userID) - - @tasks.loop(minutes=10.0) - async def checker(self): - data = json.loads(Path('./api_keys.json').read_text()) - for key, value in data.items(): - await verifyNew(self, None, key) + def __init__(self, bot): + self.bot = bot + self.checker.start() + + async def is_mod(ctx): + return ctx.author.guild_permissions.manage_channels + + @commands.command(description="Posts all pending runs to #pending-runs") + @commands.guild_only() + async def pending(self, ctx): + async with ctx.typing(): + 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") + @commands.check(is_mod) + @commands.guild_only() + async def reject( + self, + ctx, + apiKey, + run, + *, + reason="Rejected using Steve. No additional reason provided", + ): + if apiKey == None: + apiKey = self.bot.config["api_key"] + await rejectRun(self, apiKey, ctx, run, reason) + + @commands.command(description="Approve runs quickly") + @commands.check(is_mod) + @commands.guild_only() + async def approve( + self, + ctx, + apiKey, + run, + *, + reason="Approved using Steve. No additional reason provided", + ): + if apiKey == None: + apiKey = self.bot.config["api_key"] + await approveRun(self, apiKey, ctx, run, reason) + + @commands.command(description="Delete runs quickly") + async def delete(self, ctx, apiKey, run): + await deleteRun(self, apiKey, ctx, run) + + @commands.command() + async def verify(self, ctx, apiKey=None, userID=None): + 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 this command again by getting an api key from https://www.speedrun.com/api/auth then do `{ctx.prefix}verify ` 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(): + if userID == None: + userID = ctx.message.author.id + await verifyNew(self, apiKey, userID) + + @tasks.loop(minutes=10.0) + async def checker(self): + data = json.loads(Path("./api_keys.json").read_text()) + for key, value in data.items(): + await verifyNew(self, None, key) def setup(bot): - bot.add_cog(Src(bot)) + bot.add_cog(Src(bot)) diff --git a/cogs/trans.py b/cogs/trans.py index 965f68e..d2aead2 100755 --- a/cogs/trans.py +++ b/cogs/trans.py @@ -5,48 +5,74 @@ from google.cloud import translate_v2 as translate translate_client = translate.Client() + async def translateMsg(text, target="en"): - # Text can also be a sequence of strings, in which case this method - # will return a sequence of results for each text. - if isinstance(text, six.binary_type): - text = text.decode('utf-8') - result = translate_client.translate( - text, target_language=target) - print(u'Text: {}'.format(result['input'])) - print(u'Translation: {}'.format(result['translatedText'])) - print(u'Detected source language: {}'.format( - result['detectedSourceLanguage'])) - 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("<@! ", "<@!") - result['translatedText'] = result['translatedText'].replace("<@ ", "<@") - result['translatedText'] = result['translatedText'].replace("<# ", "<#") - return result; + # Text can also be a sequence of strings, in which case this method + # will return a sequence of results for each text. + if isinstance(text, six.binary_type): + text = text.decode("utf-8") + result = translate_client.translate(text, target_language=target) + print("Text: {}".format(result["input"])) + print("Translation: {}".format(result["translatedText"])) + print("Detected source language: {}".format(result["detectedSourceLanguage"])) + 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("<@! ", "<@!") + result["translatedText"] = result["translatedText"].replace("<@ ", "<@") + result["translatedText"] = result["translatedText"].replace("<# ", "<#") + return result + 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", + 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, + ) + embed.add_field( + name="Translation", value=response["translatedText"], inline=True + ) + await ctx.send(embed=embed) + + @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, + ) + embed.add_field( + name="Translation", value=response["translatedText"], inline=True + ) + await ctx.send(embed=embed) - def __init__(self, bot): - self.bot = bot - - @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) - embed.add_field(name="Translation", value=response['translatedText'], inline=True) - await ctx.send(embed=embed) - - @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) - embed.add_field(name="Translation", value=response['translatedText'], inline=True) - await ctx.send(embed=embed) def setup(bot): - bot.add_cog(Trans(bot)) + bot.add_cog(Trans(bot)) diff --git a/cogs/twitter.py b/cogs/twitter.py index 0688380..d633b8e 100755 --- a/cogs/twitter.py +++ b/cogs/twitter.py @@ -7,46 +7,55 @@ from discord.ext import commands, tasks class StreamListener(tweepy.StreamListener): - def __init__(self): - with open('./config.json') as f: - self.config = json.load(f) - - def on_error(self, status_code): - if status_code == 420: - print("Rate limit reached. ") - #returning False in on_error disconnects the stream - return False - - def on_data(self, data): - data = json.loads(data) - try: - tweetUser = data['tweet']['user']['screen_name'] - tweetID = data['tweet']['id_str'] - except: - tweetUser = data['user']['screen_name'] - tweetID = data['id_str'] - tweetLink = f'https://twitter.com/{tweetUser}/status/{tweetID}' - body = { - "content": tweetLink - } - global config - r = requests.post(self.config['574267523869179904']['tweetWebhook'], headers={"Content-Type": "application/json"}, data=json.dumps(body))#config['574267523869179904']['tweetWebhook'], data=json.dumps(body)) - print(r.status_code) - print(r.text) - #print(json.dumps(data, indent='\t')) + def __init__(self): + with open("./config.json") as f: + self.config = json.load(f) + + def on_error(self, status_code): + if status_code == 420: + print("Rate limit reached. ") + # returning False in on_error disconnects the stream + return False + + def on_data(self, data): + data = json.loads(data) + try: + tweetUser = data["tweet"]["user"]["screen_name"] + tweetID = data["tweet"]["id_str"] + except: + tweetUser = data["user"]["screen_name"] + tweetID = data["id_str"] + tweetLink = f"https://twitter.com/{tweetUser}/status/{tweetID}" + body = {"content": tweetLink} + global config + r = requests.post( + self.config["574267523869179904"]["tweetWebhook"], + headers={"Content-Type": "application/json"}, + data=json.dumps(body), + ) # config['574267523869179904']['tweetWebhook'], data=json.dumps(body)) + print(r.status_code) + print(r.text) + # print(json.dumps(data, indent='\t')) class Twitter(commands.Cog): - def __init__(self, bot): - self.bot = bot + def __init__(self, bot): + self.bot = bot - auth = tweepy.OAuthHandler(self.bot.config['twitter']['consumer_key'], self.bot.config['twitter']['consumer_secret']) - auth.set_access_token(self.bot.config['twitter']['access_token'], self.bot.config['twitter']['access_token_secret']) + auth = tweepy.OAuthHandler( + self.bot.config["twitter"]["consumer_key"], + self.bot.config["twitter"]["consumer_secret"], + ) + auth.set_access_token( + self.bot.config["twitter"]["access_token"], + self.bot.config["twitter"]["access_token_secret"], + ) + + api = tweepy.API(auth) + myStreamListener = StreamListener() + stream = tweepy.Stream(auth=api.auth, listener=myStreamListener) + stream.filter(follow=["1287799985040437254"], is_async=True) - api = tweepy.API(auth) - myStreamListener = StreamListener() - stream = tweepy.Stream(auth = api.auth, listener=myStreamListener) - stream.filter(follow=['1287799985040437254'], is_async=True) def setup(bot): - bot.add_cog(Twitter(bot)) \ No newline at end of file + bot.add_cog(Twitter(bot)) diff --git a/cogs/utils.py b/cogs/utils.py index 5f879a2..d0324fb 100755 --- a/cogs/utils.py +++ b/cogs/utils.py @@ -3,14 +3,12 @@ import datetime import functools import json from datetime import timedelta - # 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 from random import choice, randint import discord from discord.ext import commands, tasks - # from PIL.Image import core as Image # import image as Image from PIL import Image, ImageFilter diff --git a/cogs/webserver.py b/cogs/webserver.py index d9c92cf..757c8c5 100755 --- a/cogs/webserver.py +++ b/cogs/webserver.py @@ -11,48 +11,49 @@ routes = web.RouteTableDef() class Webserver(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.web_server.start() - - @routes.get('/') - async def welcome(request): - return web.Response(text="Hello, world") - - @routes.get('/keys') - async def get_keys(request): - with open('./api_keys.json') as f: - keys = json.load(f) - return web.json_response(keys) - - @routes.post('/keys') - async def post_keys(request): - data = await request.post() - try: - discord_id = data['discord_id'] - src_id = data['src_id'] - except KeyError: - return 400 - with open('./api_keys.json', 'r') as f: - keys = json.load(f) - keys[discord_id] = src_id - with open('./api_keys.json', 'w') as f: - json.dump(keys, f, indent=4) - return web.json_response(keys) - - self.webserver_port = os.environ.get('PORT', 5000) - app.add_routes(routes) - - @tasks.loop() - async def web_server(self): - runner = web.AppRunner(app) - await runner.setup() - site = web.TCPSite(runner, host='0.0.0.0', port=self.webserver_port) - await site.start() - - @web_server.before_loop - async def web_server_before_loop(self): - await self.bot.wait_until_ready() + def __init__(self, bot): + self.bot = bot + self.web_server.start() + + @routes.get("/") + async def welcome(request): + return web.Response(text="Hello, world") + + @routes.get("/keys") + async def get_keys(request): + with open("./api_keys.json") as f: + keys = json.load(f) + return web.json_response(keys) + + @routes.post("/keys") + async def post_keys(request): + data = await request.post() + try: + discord_id = data["discord_id"] + src_id = data["src_id"] + except KeyError: + return 400 + with open("./api_keys.json", "r") as f: + keys = json.load(f) + keys[discord_id] = src_id + with open("./api_keys.json", "w") as f: + json.dump(keys, f, indent=4) + return web.json_response(keys) + + self.webserver_port = os.environ.get("PORT", 5000) + app.add_routes(routes) + + @tasks.loop() + async def web_server(self): + runner = web.AppRunner(app) + await runner.setup() + site = web.TCPSite(runner, host="0.0.0.0", port=self.webserver_port) + await site.start() + + @web_server.before_loop + async def web_server_before_loop(self): + await self.bot.wait_until_ready() + def setup(bot): - bot.add_cog(Webserver(bot)) \ No newline at end of file + bot.add_cog(Webserver(bot)) -- cgit v1.2.3