From 7ab1449c7926913b591ee0682e4fb920698a9a48 Mon Sep 17 00:00:00 2001 From: fepfitra Date: Wed, 13 Nov 2024 23:17:27 +0700 Subject: [PATCH 1/7] fix: intents error --- nullctf.py | 107 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 32 deletions(-) diff --git a/nullctf.py b/nullctf.py index 8a257e4..0085660 100644 --- a/nullctf.py +++ b/nullctf.py @@ -7,19 +7,48 @@ import help_info import config_vars -client = discord.Client() -bot = commands.Bot(command_prefix=">", allowed_mentions = discord.AllowedMentions(everyone = False, users=False, roles=False)) +intents = discord.Intents().all() +intents.members = True + +client = discord.Client(intents=intents) +bot = commands.Bot( + command_prefix=">", + intents=intents, + allowed_mentions=discord.AllowedMentions(everyone=False, users=False, roles=False), +) # The default help command is removed so a custom one can be added. -bot.remove_command('help') +bot.remove_command("help") # Each extension corresponds to a file within the cogs directory. Remove from the list to take away the functionality. -extensions = ['ctf', 'ctftime', 'configuration', 'encoding', 'cipher', 'utility'] +# extensions = ["ctf", "ctftime", "configuration", "encoding", "cipher", "utility"] +extensions = ["ctftime", "configuration", "encoding", "cipher", "utility"] # List of names reserved for those who gave cool ideas or reported something interesting. - # please don't spam me asking to be added. if you send something interesting to me i will add you to the list. +# please don't spam me asking to be added. if you send something interesting to me i will add you to the list. # If your name is in the list and you use the command '>amicool' you'll get a nice message. -cool_names = ['nullpxl', 'Yiggles', 'JohnHammond', 'voidUpdate', 'Michel Ney', 'theKidOfArcrania', 'l14ck3r0x01', 'hasu', 'KFBI', 'mrFu', 'warlock_rootx', 'd347h4ck', 'tourpan', 'careless_finch', 'fumenoid', '_wh1t3r0se_', 'The_Crazyman','0x0elliot'] +cool_names = [ + "fitrafep", + "nullpxl", + "Yiggles", + "JohnHammond", + "voidUpdate", + "Michel Ney", + "theKidOfArcrania", + "l14ck3r0x01", + "hasu", + "KFBI", + "mrFu", + "warlock_rootx", + "d347h4ck", + "tourpan", + "careless_finch", + "fumenoid", + "_wh1t3r0se_", + "The_Crazyman", + "0x0elliot", +] # This is intended to be circumvented; the idea being that people will change their names to people in this list just so >amicool works for them, and I think that's funny. + @bot.event async def on_ready(): print(f"{bot.user.name} - Online") @@ -28,39 +57,43 @@ async def on_ready(): await bot.change_presence(activity=discord.Game(name=">help | >source")) + @bot.command() async def help(ctx, page=None): # Custom help command. Each main category is set as a 'page'. - if page == 'ctftime': + if page == "ctftime": emb = discord.Embed(description=help_info.ctftime_help, colour=4387968) - emb.set_author(name='CTFTime Help') - elif page == 'ctf': - emb = discord.Embed(description=help_info.ctf_help, colour=4387968) - emb.set_author(name='CTF Help') - elif page == 'config': + emb.set_author(name="CTFTime Help") + elif page == "ctf": + # emb = discord.Embed(description=help_info.ctf_help, colour=4387968) + emb = discord.Embed(description="Disabled by admin", colour=4387968) + emb.set_author(name="CTF Help") + elif page == "config": emb = discord.Embed(description=help_info.config_help, colour=4387968) - emb.set_author(name='Configuration Help') - elif page == 'utility': + emb.set_author(name="Configuration Help") + elif page == "utility": emb = discord.Embed(description=help_info.utility_help, colour=4387968) - emb.set_author(name='Utilities Help') - + emb.set_author(name="Utilities Help") + else: emb = discord.Embed(description=help_info.help_page, colour=4387968) - emb.set_author(name='NullCTF Help') + emb.set_author(name="NullCTF Help") - await attach_embed_info(ctx, emb) + # await attach_embed_info(ctx, emb) await ctx.channel.send(embed=emb) -async def attach_embed_info(ctx=None, embed=None): - embed.set_thumbnail(url=f'{bot.user.avatar_url}') - return embed +# async def attach_embed_info(ctx=None, embed=None): +# embed.set_thumbnail(url=f"{bot.user.avatar_url}") +# return embed + @bot.command() async def source(ctx): # Sends the github link of the bot. await ctx.send(help_info.src) + @bot.event async def on_command_error(ctx, error): if isinstance(error, commands.CommandNotFound): @@ -68,43 +101,53 @@ async def on_command_error(ctx, error): if isinstance(error, commands.MissingRequiredArgument): await ctx.send("Missing a required argument. Do >help") if isinstance(error, commands.MissingPermissions): - await ctx.send("You do not have the appropriate permissions to run this command.") + await ctx.send( + "You do not have the appropriate permissions to run this command." + ) if isinstance(error, commands.BotMissingPermissions): await ctx.send("I don't have sufficient permissions!") else: print("error not caught") print(error) + @bot.command() async def request(ctx, feature): # Bot sends a dm to creator with the name of the user and their request. creator = await bot.fetch_user(230827776637272064) authors_name = str(ctx.author) - await creator.send(f''':pencil: {authors_name}: {feature}''') - await ctx.send(f''':pencil: Thanks, "{feature}" has been requested!''') + await creator.send(f""":pencil: {authors_name}: {feature}""") + await ctx.send(f""":pencil: Thanks, "{feature}" has been requested!""") + @bot.command() async def report(ctx, error_report): # Bot sends a dm to creator with the name of the user and their report. creator = await bot.fetch_user(230827776637272064) authors_name = str(ctx.author) - await creator.send(f''':triangular_flag_on_post: {authors_name}: {error_report}''') - await ctx.send(f''':triangular_flag_on_post: Thanks for the help, "{error_report}" has been reported!''') + await creator.send(f""":triangular_flag_on_post: {authors_name}: {error_report}""") + await ctx.send( + f""":triangular_flag_on_post: Thanks for the help, "{error_report}" has been reported!""" + ) + @bot.command() async def amicool(ctx): authors_name = str(ctx.author).split("#")[0] if authors_name in cool_names: - await ctx.send('You are very cool :]') + await ctx.send("You are very cool :]") else: - await ctx.send('lolno') - await ctx.send('Psst, kid. Want to be cool? Find an issue and report it or request a feature!') + await ctx.send("lolno") + await ctx.send( + "Psst, kid. Want to be cool? Find an issue and report it or request a feature!" + ) + -if __name__ == '__main__': - sys.path.insert(1, os.getcwd() + '/cogs/') +if __name__ == "__main__": + sys.path.insert(1, os.getcwd() + "/cogs/") for extension in extensions: try: bot.load_extension(extension) except Exception as e: - print(f'Failed to load cogs : {e}') + print(f"Failed to load cogs : {e}") bot.run(config_vars.discord_token) From d9326119e96f6d960e6ed6aa21669c7eb89a821e Mon Sep 17 00:00:00 2001 From: fepfitra Date: Wed, 13 Nov 2024 23:25:47 +0700 Subject: [PATCH 2/7] update docker python version --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7363011..1c188c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.7.8-alpine +FROM python:3.10.15-bookworm RUN mkdir -p /usr/src/app WORKDIR /usr/src/app @@ -14,4 +14,4 @@ COPY requirements.txt . RUN pip install -r requirements.txt -CMD ["python", "nullctf.py"] \ No newline at end of file +CMD ["python", "nullctf.py"] From f6be641c1e2979810ddab5e2c0cff94d48b6f7e3 Mon Sep 17 00:00:00 2001 From: fepfitra Date: Wed, 13 Nov 2024 23:26:28 +0700 Subject: [PATCH 3/7] I use uv btw --- pyproject.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6b424a4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "nullctf" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "discord>=2.3.2", + "pymongo>=4.10.1", +] From ce648549f7b63ac84d834f9252e513efd7c7731b Mon Sep 17 00:00:00 2001 From: fepfitra Date: Wed, 13 Nov 2024 23:26:33 +0700 Subject: [PATCH 4/7] disable --- help_info.py | 51 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/help_info.py b/help_info.py index a2d81c0..479af38 100644 --- a/help_info.py +++ b/help_info.py @@ -1,4 +1,4 @@ -ctftime_help = ''' +ctftime_help = """ `>ctftime upcoming [1-5]` return info on a number of upcoming ctfs from ctftime.org @@ -11,9 +11,9 @@ `>ctftime top [year]` display the leaderboards from ctftime from a certain year. -''' +""" -ctf_help = ''' +ctf_help = """ `>ctf create "CTF NAME"` create a text channel and role in the CTF category for a ctf (must have permissions to manage channels)* @@ -41,18 +41,18 @@ `>ctf delete` delete the ctf role, and entry from the database for the ctf (must have permissions to manage channels)* -''' +""" -config_help = ''' +config_help = """ `>config ctf_category "CTF CATEGORY"` specify the category to be used for CTF channels, defaults to "CTF". `>config archive_category "ARCHIVE CATEGORY"` specify the category to be used for archived CTF channels, defaults to "Archive". -''' +""" -utility_help = ''' +utility_help = """ `>magicb [filetype]` return the magicbytes/file header of a supplied filetype. `>rot "message"` @@ -85,26 +85,39 @@ get a 50/50 cointoss to make all your life's decisions `>amicool` for the truth. -''' - - -help_page = ''' +""" + + +# help_page = ''' +# +# `>help ctftime` +# info for all ctftime commands +# +# `>help ctf` +# info for all ctf commands +# +# `>help config` +# bot configuration info +# +# `>help utility` +# everything else! (basically misc) +# +# `>report/request "an issue or feature"` +# report an issue, or request a feature for NullCTF, if it is helpful your name will be added to the 'cool names' list! +# ''' + +help_page = """ `>help ctftime` info for all ctftime commands -`>help ctf` -info for all ctf commands - -`>help config` -bot configuration info - `>help utility` everything else! (basically misc) `>report/request "an issue or feature"` report an issue, or request a feature for NullCTF, if it is helpful your name will be added to the 'cool names' list! -''' +""" + +src = "https://github.com/NullPxl/NullCTF" -src = "https://github.com/NullPxl/NullCTF" \ No newline at end of file From 150a2c288bc86a889c1142c01fa615e7b14ce859 Mon Sep 17 00:00:00 2001 From: fepfitra Date: Wed, 13 Nov 2024 23:26:43 +0700 Subject: [PATCH 5/7] uv venv python version --- .python-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..c8cfe39 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 From a594c3eedf2ec5af2a47663301f7926375fc7761 Mon Sep 17 00:00:00 2001 From: fepfitra Date: Wed, 13 Nov 2024 23:34:18 +0700 Subject: [PATCH 6/7] fix alpine --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1c188c8..6f7a5c9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10.15-bookworm +FROM python:3.10.15-alpine RUN mkdir -p /usr/src/app WORKDIR /usr/src/app From 5f47c5b9966186267f7ee15496a94a30a6834c44 Mon Sep 17 00:00:00 2001 From: fepfitra Date: Thu, 14 Nov 2024 00:07:17 +0700 Subject: [PATCH 7/7] it's 2024 --- Dockerfile | 2 +- cogs/cipher.py | 36 ++--- cogs/configuration.py | 49 ++++--- cogs/ctf.py | 306 ++++++++++++++++++++++++++++-------------- cogs/ctftime.py | 302 ++++++++++++++++++++++++++--------------- cogs/encoding.py | 65 ++++----- cogs/utility.py | 54 ++++---- nullctf.py | 10 +- pyproject.toml | 3 + 9 files changed, 524 insertions(+), 303 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1c188c8..35a7e41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10.15-bookworm +FROM python:3.7.8-alpine RUN mkdir -p /usr/src/app WORKDIR /usr/src/app diff --git a/cogs/cipher.py b/cogs/cipher.py index b8e0ce8..482e3f2 100644 --- a/cogs/cipher.py +++ b/cogs/cipher.py @@ -3,38 +3,42 @@ import discord from discord.ext import commands -class Ciphers(commands.Cog): +class Ciphers(commands.Cog): def __init__(self, bot): self.bot = bot @commands.command() async def rot(self, ctx, message, direction=None): # Bruteforce a rot cipher. - allrot = '' - + allrot = "" + for i in range(0, 26): upper = collections.deque(string.ascii_uppercase) lower = collections.deque(string.ascii_lowercase) - - upper.rotate((- i)) - lower.rotate((- i)) - - upper = ''.join(list(upper)) - lower = ''.join(list(lower)) - translated = message.translate(str.maketrans(string.ascii_uppercase, upper)).translate(str.maketrans(string.ascii_lowercase, lower)) - allrot += '{}: {}\n'.format(i, translated) - + + upper.rotate((-i)) + lower.rotate((-i)) + + upper = "".join(list(upper)) + lower = "".join(list(lower)) + translated = message.translate( + str.maketrans(string.ascii_uppercase, upper) + ).translate(str.maketrans(string.ascii_lowercase, lower)) + allrot += "{}: {}\n".format(i, translated) + await ctx.send(f"```{allrot}```") @commands.command() async def atbash(self, ctx, message): # Return the result of performing the atbash cipher on the message. - normal = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' - changed = 'zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA' + normal = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + changed = "zyxwvutsrqponmlkjihgfedcbaZYXWVUTSRQPONMLKJIHGFEDCBA" trans = str.maketrans(normal, changed) atbashed = message.translate(trans) await ctx.send(atbashed) -def setup(bot): - bot.add_cog(Ciphers(bot)) \ No newline at end of file + +async def setup(bot): + await bot.add_cog(Ciphers(bot)) + diff --git a/cogs/configuration.py b/cogs/configuration.py index 9c2cdb2..815996e 100644 --- a/cogs/configuration.py +++ b/cogs/configuration.py @@ -1,12 +1,14 @@ import discord from discord.ext import tasks, commands import sys + sys.path.append("..") from config_vars import * # Extension for per-discord-server configuration. # Configurations are logged in the database under the server id (right click on your server icon in discord dev mode). + class Configuration(commands.Cog): def __init__(self, bot): self.bot = bot @@ -15,9 +17,11 @@ def __init__(self, bot): async def config(self, ctx): if ctx.invoked_subcommand is None: # If the subcommand passed does not exist, its type is None - config_commands = list(set([c.qualified_name for c in Configuration.walk_commands(self)][1:])) + config_commands = list( + set([c.qualified_name for c in Configuration.walk_commands(self)][1:]) + ) await ctx.send(f"Current config commands are: {', '.join(config_commands)}") - + @commands.bot_has_permissions(manage_channels=True) @commands.has_permissions(manage_channels=True) @config.command() @@ -25,17 +29,21 @@ async def ctf_category(self, ctx, category_name): # Set the category that new ctf channels are created in by default. category_name = category_name.replace("$", "") category = discord.utils.get(ctx.guild.categories, name=category_name) - - if category == None: # Checks if category exists, if it doesn't it will create it. + + if ( + category == None + ): # Checks if category exists, if it doesn't it will create it. await ctx.guild.create_category(name=category_name) category = discord.utils.get(ctx.guild.categories, name=category_name) - - sconf = serverdb[str(ctx.guild.id) + '-CONF'] # sconf means server configuration + + sconf = serverdb[ + str(ctx.guild.id) + "-CONF" + ] # sconf means server configuration info = {"ctf_category": category_name} - sconf.update({"name": 'category_name'}, {"$set": info}, upsert=True) - categoryset = sconf.find_one({'name': "category_name"})['ctf_category'] + sconf.update({"name": "category_name"}, {"$set": info}, upsert=True) + categoryset = sconf.find_one({"name": "category_name"})["ctf_category"] await ctx.send(f"CTF category set as `{categoryset}`") - + @commands.bot_has_permissions(manage_channels=True) @commands.has_permissions(manage_channels=True) @config.command() @@ -43,17 +51,24 @@ async def archive_category(self, ctx, category_name): # Set the category that archived ctf channels are put in by default. category_name = category_name.replace("$", "") category = discord.utils.get(ctx.guild.categories, name=category_name) - - if category == None: # Checks if category exists, if it doesn't it will create it. + + if ( + category == None + ): # Checks if category exists, if it doesn't it will create it. await ctx.guild.create_category(name=category_name) category = discord.utils.get(ctx.guild.categories, name=category_name) - - sconf = serverdb[str(ctx.guild.id) + '-CONF'] # sconf means server configuration + + sconf = serverdb[ + str(ctx.guild.id) + "-CONF" + ] # sconf means server configuration info = {"archive_category": category_name} - sconf.update({"name": 'archive_category_name'}, {"$set": info}, upsert=True) - categoryset = sconf.find_one({'name': "archive_category_name"})['archive_category'] + sconf.update({"name": "archive_category_name"}, {"$set": info}, upsert=True) + categoryset = sconf.find_one({"name": "archive_category_name"})[ + "archive_category" + ] await ctx.send(f"Archive category set as `{categoryset}`") -def setup(bot): - bot.add_cog(Configuration(bot)) \ No newline at end of file +async def setup(bot): + await bot.add_cog(Configuration(bot)) + diff --git a/cogs/ctf.py b/cogs/ctf.py index dac2628..ebd52f3 100644 --- a/cogs/ctf.py +++ b/cogs/ctf.py @@ -5,43 +5,73 @@ import requests import sys import traceback + sys.path.append("..") from config_vars import * # All commands relating to server specific CTF data # Credentials provided for pulling challenges from the CTFd platform are NOT stored in the database. - # they are stored in a pinned message in the discord channel. +# they are stored in a pinned message in the discord channel. + def in_ctf_channel(): async def tocheck(ctx): # A check for ctf context specific commands - if teamdb[str(ctx.guild.id)].find_one({'name': str(ctx.message.channel)}): + if teamdb[str(ctx.guild.id)].find_one({"name": str(ctx.message.channel)}): return True else: await ctx.send("You must be in a created ctf channel to use ctf commands!") return False + return commands.check(tocheck) + def strip_string(tostrip, whitelist): # A string validator to correspond with a provided whitelist. - stripped = ''.join([ch for ch in tostrip if ch in whitelist]) + stripped = "".join([ch for ch in tostrip if ch in whitelist]) return stripped.strip() + class InvalidProvider(Exception): pass + + class InvalidCredentials(Exception): pass + + class CredentialsNotFound(Exception): pass + + class NonceNotFound(Exception): pass + def getChallenges(url, username, password): # Pull challenges from a ctf hosted with the commonly used CTFd platform using provided credentials - whitelist = set(string.ascii_letters+string.digits+' '+'-'+'!'+'#'+'_'+'['+']'+'('+')'+'?'+'@'+'+'+'<'+'>') + whitelist = set( + string.ascii_letters + + string.digits + + " " + + "-" + + "!" + + "#" + + "_" + + "[" + + "]" + + "(" + + ")" + + "?" + + "@" + + "+" + + "<" + + ">" + ) fingerprint = "Powered by CTFd" s = requests.session() - if url[-1] == "/": url = url[:-1] + if url[-1] == "/": + url = url[:-1] r = s.get(f"{url}/login") if fingerprint not in r.text: raise InvalidProvider("CTF is not based on CTFd, cannot pull challenges.") @@ -49,59 +79,67 @@ def getChallenges(url, username, password): # Get the nonce from the login page. try: nonce = r.text.split("csrfNonce': \"")[1].split('"')[0] - except: # sometimes errors happen here, my theory is that it is different versions of CTFd + except: # sometimes errors happen here, my theory is that it is different versions of CTFd try: - nonce = r.text.split("name=\"nonce\" value=\"")[1].split('">')[0] + nonce = r.text.split('name="nonce" value="')[1].split('">')[0] except: - raise NonceNotFound("Was not able to find the nonce token from login, please >report this along with the ctf url.") + raise NonceNotFound( + "Was not able to find the nonce token from login, please >report this along with the ctf url." + ) # Login with the username, password, and nonce - r = s.post(f"{url}/login", data={"name": username, "password": password, "nonce": nonce}) + r = s.post( + f"{url}/login", + data={"name": username, "password": password, "nonce": nonce}, + ) if "Your username or password is incorrect" in r.text: raise InvalidCredentials("Invalid login credentials") r_chals = s.get(f"{url}/api/v1/challenges") all_challenges = r_chals.json() r_solves = s.get(f"{url}/api/v1/teams/me/solves") team_solves = r_solves.json() - if 'success' not in team_solves: + if "success" not in team_solves: # ctf is user based. There is a flag on CTFd for this (userMode), but it is not present in all versions, this way seems to be. r_solves = s.get(f"{url}/api/v1/users/me/solves") team_solves = r_solves.json() - + solves = [] - if team_solves['success'] == True: - for solve in team_solves['data']: - cat = solve['challenge']['category'] - challname = solve['challenge']['name'] + if team_solves["success"] == True: + for solve in team_solves["data"]: + cat = solve["challenge"]["category"] + challname = solve["challenge"]["name"] solves.append(f"<{cat}> {challname}") challenges = {} - if all_challenges['success'] == True: - for chal in all_challenges['data']: - cat = chal['category'] - challname = chal['name'] + if all_challenges["success"] == True: + for chal in all_challenges["data"]: + cat = chal["category"] + challname = chal["name"] name = f"<{cat}> {challname}" # print(name) # print(strip_string(name, whitelist)) if name not in solves: - challenges.update({strip_string(name, whitelist): 'Unsolved'}) + challenges.update({strip_string(name, whitelist): "Unsolved"}) else: - challenges.update({strip_string(name, whitelist): 'Solved'}) + challenges.update({strip_string(name, whitelist): "Solved"}) else: raise Exception("Error making request") # Returns all the new challenges and their corresponding statuses in a dictionary compatible with the structure that would happen with 'normal' useage. return challenges - class CTF(commands.Cog): def __init__(self, bot): self.bot = bot - + @commands.group() async def ctf(self, ctx): if ctx.invoked_subcommand is None: # If the subcommand passed does not exist, its type is None - ctf_commands = list(set([c.qualified_name for c in CTF.walk_commands(self)][1:])) - await ctx.send(f"Current ctf commands are: {', '.join(ctf_commands)}") # update this to include params + ctf_commands = list( + set([c.qualified_name for c in CTF.walk_commands(self)][1:]) + ) + await ctx.send( + f"Current ctf commands are: {', '.join(ctf_commands)}" + ) # update this to include params @commands.bot_has_permissions(manage_channels=True, manage_roles=True) @commands.has_permissions(manage_channels=True) @@ -109,36 +147,45 @@ async def ctf(self, ctx): async def create(self, ctx, name): # Create a new channel in the CTF category (default='CTF' or configured with the configuration extension) try: - sconf = serverdb[str(ctx.guild.id) + '-CONF'] - servcat = sconf.find_one({'name': "category_name"})['ctf_category'] + sconf = serverdb[str(ctx.guild.id) + "-CONF"] + servcat = sconf.find_one({"name": "category_name"})["ctf_category"] except: servcat = "CTF" - + category = discord.utils.get(ctx.guild.categories, name=servcat) - if category == None: # Checks if category exists, if it doesn't it will create it. + if ( + category == None + ): # Checks if category exists, if it doesn't it will create it. await ctx.guild.create_category(name=servcat) category = discord.utils.get(ctx.guild.categories, name=servcat) - - ctf_name = strip_string(name, set(string.ascii_letters + string.digits + ' ' + '-')).replace(' ', '-').lower() - if ctf_name[0] == '-': ctf_name = ctf_name[1:] # edge case where channel names can't start with a space (but can end in one) + + ctf_name = ( + strip_string(name, set(string.ascii_letters + string.digits + " " + "-")) + .replace(" ", "-") + .lower() + ) + if ctf_name[0] == "-": + ctf_name = ctf_name[ + 1: + ] # edge case where channel names can't start with a space (but can end in one) # There cannot be 2 spaces (which are converted to '-') in a row when creating a channel. This makes sure these are taken out. new_ctf_name = ctf_name - prev = '' - while '--' in ctf_name: + prev = "" + while "--" in ctf_name: for i, c in enumerate(ctf_name): - if c == prev and c == '-': - new_ctf_name = ctf_name[:i] + ctf_name[i+1:] + if c == prev and c == "-": + new_ctf_name = ctf_name[:i] + ctf_name[i + 1 :] prev = c ctf_name = new_ctf_name - + await ctx.guild.create_text_channel(name=ctf_name, category=category) server = teamdb[str(ctx.guild.id)] - await ctx.guild.create_role(name=ctf_name, mentionable=True) - ctf_info = {'name': ctf_name, "text_channel": ctf_name} - server.update({'name': ctf_name}, {"$set": ctf_info}, upsert=True) + await ctx.guild.create_role(name=ctf_name, mentionable=True) + ctf_info = {"name": ctf_name, "text_channel": ctf_name} + server.update({"name": ctf_name}, {"$set": ctf_info}, upsert=True) # Give a visual confirmation of completion. await ctx.message.add_reaction("✅") - + @commands.bot_has_permissions(manage_channels=True, manage_roles=True) @commands.has_permissions(manage_channels=True) @ctf.command() @@ -149,11 +196,11 @@ async def delete(self, ctx): role = discord.utils.get(ctx.guild.roles, name=str(ctx.message.channel)) await role.delete() await ctx.send(f"`{role.name}` role deleted") - except: # role most likely already deleted with archive + except: # role most likely already deleted with archive pass - teamdb[str(ctx.guild.id)].remove({'name': str(ctx.message.channel)}) + teamdb[str(ctx.guild.id)].remove({"name": str(ctx.message.channel)}) await ctx.send(f"`{str(ctx.message.channel)}` deleted from db") - + @commands.bot_has_permissions(manage_channels=True, manage_roles=True) @commands.has_permissions(manage_channels=True) @ctf.command(aliases=["over"]) @@ -164,25 +211,31 @@ async def archive(self, ctx): await role.delete() await ctx.send(f"`{role.name}` role deleted, archiving channel.") try: - sconf = serverdb[str(ctx.guild.id) + '-CONF'] - servarchive = sconf.find_one({'name': "archive_category_name"})['archive_category'] + sconf = serverdb[str(ctx.guild.id) + "-CONF"] + servarchive = sconf.find_one({"name": "archive_category_name"})[ + "archive_category" + ] except: - servarchive = "ARCHIVE" # default + servarchive = "ARCHIVE" # default category = discord.utils.get(ctx.guild.categories, name=servarchive) - if category == None: # Checks if category exists, if it doesn't it will create it. + if ( + category == None + ): # Checks if category exists, if it doesn't it will create it. await ctx.guild.create_category(name=servarchive) category = discord.utils.get(ctx.guild.categories, name=servarchive) await ctx.message.channel.edit(syncpermissoins=True, category=category) - + @ctf.command() @in_ctf_channel() async def end(self, ctx): # This command is deprecated, but due to getting so many DMs from people who didn't use >help, I've decided to just have this as my solution. - await ctx.send("You can now use either `>ctf delete` (which will delete all data), or `>ctf archive/over` \ + await ctx.send( + 'You can now use either `>ctf delete` (which will delete all data), or `>ctf archive/over` \ which will move the channel and delete the role, but retain challenge info(`>config archive_category \ -\"archive category\"` to specify where to archive.") - +"archive category"` to specify where to archive.' + ) + @commands.bot_has_permissions(manage_roles=True) @ctf.command() @in_ctf_channel() @@ -192,7 +245,7 @@ async def join(self, ctx): user = ctx.message.author await user.add_roles(role) await ctx.send(f"{user} has joined the {str(ctx.message.channel)} team!") - + @commands.bot_has_permissions(manage_roles=True) @ctf.command() @in_ctf_channel() @@ -202,66 +255,103 @@ async def leave(self, ctx): user = ctx.message.author await user.remove_roles(role) await ctx.send(f"{user} has left the {str(ctx.message.channel)} team.") - + @ctf.group(aliases=["chal", "chall", "challenges"]) @in_ctf_channel() async def challenge(self, ctx): pass - + @staticmethod def updateChallenge(ctx, name, status): # Update the db with a new challenge and its status server = teamdb[str(ctx.guild.id)] - whitelist = set(string.ascii_letters+string.digits+' '+'-'+'!'+'#'+'_'+'['+']'+'('+')'+'?'+'@'+'+'+'<'+'>') + whitelist = set( + string.ascii_letters + + string.digits + + " " + + "-" + + "!" + + "#" + + "_" + + "[" + + "]" + + "(" + + ")" + + "?" + + "@" + + "+" + + "<" + + ">" + ) challenge = {strip_string(str(name), whitelist): status} - ctf = server.find_one({'name': str(ctx.message.channel)}) - try: # If there are existing challenges already... - challenges = ctf['challenges'] + ctf = server.find_one({"name": str(ctx.message.channel)}) + try: # If there are existing challenges already... + challenges = ctf["challenges"] challenges.update(challenge) except: challenges = challenge - ctf_info = {'name': str(ctx.message.channel), - 'challenges': challenges - } - server.update({'name': str(ctx.message.channel)}, {"$set": ctf_info}, upsert=True) + ctf_info = {"name": str(ctx.message.channel), "challenges": challenges} + server.update( + {"name": str(ctx.message.channel)}, {"$set": ctf_info}, upsert=True + ) - @challenge.command(aliases=["a"]) @in_ctf_channel() async def add(self, ctx, name): - CTF.updateChallenge(ctx, name, 'Unsolved') - await ctx.send(f"`{name}` has been added to the challenge list for `{str(ctx.message.channel)}`") - - @challenge.command(aliases=['s', 'solve']) + CTF.updateChallenge(ctx, name, "Unsolved") + await ctx.send( + f"`{name}` has been added to the challenge list for `{str(ctx.message.channel)}`" + ) + + @challenge.command(aliases=["s", "solve"]) @in_ctf_channel() async def solved(self, ctx, name): solve = f"Solved - {str(ctx.message.author)}" CTF.updateChallenge(ctx, name, solve) - await ctx.send(f":triangular_flag_on_post: `{name}` has been solved by `{str(ctx.message.author)}`") - - @challenge.command(aliases=['w']) + await ctx.send( + f":triangular_flag_on_post: `{name}` has been solved by `{str(ctx.message.author)}`" + ) + + @challenge.command(aliases=["w"]) @in_ctf_channel() async def working(self, ctx, name): work = f"Working - {str(ctx.message.author)}" CTF.updateChallenge(ctx, name, work) await ctx.send(f"`{str(ctx.message.author)}` is working on `{name}`!") - - @challenge.command(aliases=['r', 'delete', 'd']) + + @challenge.command(aliases=["r", "delete", "d"]) @in_ctf_channel() async def remove(self, ctx, name): # Typos can happen (remove a ctf challenge from the list) - ctf = teamdb[str(ctx.guild.id)].find_one({'name': str(ctx.message.channel)}) - challenges = ctf['challenges'] - whitelist = set(string.ascii_letters+string.digits+' '+'-'+'!'+'#'+'_'+'['+']'+'('+')'+'?'+'@'+'+'+'<'+'>') + ctf = teamdb[str(ctx.guild.id)].find_one({"name": str(ctx.message.channel)}) + challenges = ctf["challenges"] + whitelist = set( + string.ascii_letters + + string.digits + + " " + + "-" + + "!" + + "#" + + "_" + + "[" + + "]" + + "(" + + ")" + + "?" + + "@" + + "+" + + "<" + + ">" + ) name = strip_string(name, whitelist) challenges.pop(name, None) - ctf_info = {'name': str(ctx.message.channel), - 'challenges': challenges - } - teamdb[str(ctx.guild.id)].update({'name': str(ctx.message.channel)}, {"$set": ctf_info}, upsert=True) + ctf_info = {"name": str(ctx.message.channel), "challenges": challenges} + teamdb[str(ctx.guild.id)].update( + {"name": str(ctx.message.channel)}, {"$set": ctf_info}, upsert=True + ) await ctx.send(f"Removed `{name}`") - - @challenge.command(aliases=['get', 'ctfd']) + + @challenge.command(aliases=["get", "ctfd"]) @in_ctf_channel() async def pull(self, ctx, url): # Pull challenges from a ctf hosted on the CTFd platform @@ -273,16 +363,16 @@ async def pull(self, ctx, url): except CredentialsNotFound as cnfm: await ctx.send(cnfm) ctfd_challs = getChallenges(url, user_pass[0], user_pass[1]) - ctf = teamdb[str(ctx.guild.id)].find_one({'name': str(ctx.message.channel)}) - try: # If there are existing challenges already... - challenges = ctf['challenges'] + ctf = teamdb[str(ctx.guild.id)].find_one({"name": str(ctx.message.channel)}) + try: # If there are existing challenges already... + challenges = ctf["challenges"] challenges.update(ctfd_challs) except: challenges = ctfd_challs - ctf_info = {'name': str(ctx.message.channel), - 'challenges': challenges - } - teamdb[str(ctx.guild.id)].update({'name': str(ctx.message.channel)}, {"$set": ctf_info}, upsert=True) + ctf_info = {"name": str(ctx.message.channel), "challenges": challenges} + teamdb[str(ctx.guild.id)].update( + {"name": str(ctx.message.channel)}, {"$set": ctf_info}, upsert=True + ) await ctx.message.add_reaction("✅") except InvalidProvider as ipm: await ctx.send(ipm) @@ -297,7 +387,7 @@ async def pull(self, ctx, url): @commands.bot_has_permissions(manage_messages=True) @commands.has_permissions(manage_messages=True) - @ctf.command(aliases=['login']) + @ctf.command(aliases=["login"]) @in_ctf_channel() async def setcreds(self, ctx, username, password): # Creates a pinned message with the credntials supplied by the user @@ -306,11 +396,13 @@ async def setcreds(self, ctx, username, password): if "CTF credentials set." in pin.content: # Look for previously pinned credntials, and remove them if they exist. await pin.unpin() - msg = await ctx.send(f"CTF credentials set. name:{username} password:{password}") + msg = await ctx.send( + f"CTF credentials set. name:{username} password:{password}" + ) await msg.pin() - + @commands.bot_has_permissions(manage_messages=True) - @ctf.command(aliases=['getcreds']) + @ctf.command(aliases=["getcreds"]) @in_ctf_channel() async def creds(self, ctx): # Send a message with the credntials @@ -327,7 +419,9 @@ def get_creds(pinned): if "CTF credentials set." in pin.content: user_pass = pin.content.split("name:")[1].split(" password:") return user_pass - raise CredentialsNotFound("Set credentials with `>ctf setcreds \"username\" \"password\"`") + raise CredentialsNotFound( + 'Set credentials with `>ctf setcreds "username" "password"`' + ) @staticmethod def gen_page(challengelist): @@ -339,9 +433,9 @@ def gen_page(challengelist): # This will create a new message every 2k characters. if not len(challenge_page + c) >= 1989: challenge_page += c - if c == challengelist[-1]: # if it is the last item + if c == challengelist[-1]: # if it is the last item challenge_pages.append(challenge_page) - + elif len(challenge_page + c) >= 1989: challenge_pages.append(challenge_page) challenge_page = "" @@ -350,26 +444,30 @@ def gen_page(challengelist): # print(challenge_pages) return challenge_pages - @challenge.command(aliases=['ls', 'l']) + @challenge.command(aliases=["ls", "l"]) @in_ctf_channel() async def list(self, ctx): # list the challenges in the current ctf. ctf_challenge_list = [] server = teamdb[str(ctx.guild.id)] - ctf = server.find_one({'name': str(ctx.message.channel)}) + ctf = server.find_one({"name": str(ctx.message.channel)}) try: ctf_challenge_list = [] - for k, v in ctf['challenges'].items(): + for k, v in ctf["challenges"].items(): challenge = f"[{k}]: {v}\n" ctf_challenge_list.append(challenge) - + for page in CTF.gen_page(ctf_challenge_list): await ctx.send(f"```ini\n{page}```") # ```ini``` makes things in '[]' blue which looks nice :) - except KeyError as e: # If nothing has been added to the challenges list - await ctx.send("Add some challenges with `>ctf challenge add \"challenge name\"`") + except KeyError as e: # If nothing has been added to the challenges list + await ctx.send( + 'Add some challenges with `>ctf challenge add "challenge name"`' + ) except: traceback.print_exc() -def setup(bot): - bot.add_cog(CTF(bot)) \ No newline at end of file + +async def setup(bot): + await bot.add_cog(CTF(bot)) + diff --git a/cogs/ctftime.py b/cogs/ctftime.py index 6d3b220..e1ad6fb 100644 --- a/cogs/ctftime.py +++ b/cogs/ctftime.py @@ -2,27 +2,28 @@ import discord from discord.ext import tasks, commands from datetime import * -from dateutil.parser import parse # pip install python-dateutil +from dateutil.parser import parse # pip install python-dateutil import requests from colorama import Fore, Style import sys + sys.path.append("..") from config_vars import * # All commands for getting data from ctftime.org (a popular platform for finding CTF events) -class CtfTime(commands.Cog): +class CtfTime(commands.Cog): def __init__(self, bot): self.bot = bot self.upcoming_l = [] - self.updateDB.start() # pylint: disable=no-member + self.updateDB.start() # pylint: disable=no-member async def cog_command_error(self, ctx, error): print(error) - + def cog_unload(self): - self.updateDB.cancel() # pylint: disable=no-member + self.updateDB.cancel() # pylint: disable=no-member @tasks.loop(minutes=30.0, reconnect=True) async def updateDB(self): @@ -32,100 +33,134 @@ async def updateDB(self): now = datetime.utcnow() unix_now = int(now.replace(tzinfo=timezone.utc).timestamp()) headers = { - 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0', - } - upcoming = 'https://ctftime.org/api/v1/events/' - limit = '5' # Max amount I can grab the json data for + "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0", + } + upcoming = "https://ctftime.org/api/v1/events/" + limit = "5" # Max amount I can grab the json data for response = requests.get(upcoming, headers=headers, params=limit) jdata = response.json() - + info = [] - for num, i in enumerate(jdata): # Generate list of dicts of upcoming ctfs - ctf_title = jdata[num]['title'] - (ctf_start, ctf_end) = (parse(jdata[num]['start'].replace('T', ' ').split('+', 1)[0]), parse(jdata[num]['finish'].replace('T', ' ').split('+', 1)[0])) - (unix_start, unix_end) = (int(ctf_start.replace(tzinfo=timezone.utc).timestamp()), int(ctf_end.replace(tzinfo=timezone.utc).timestamp())) - dur_dict = jdata[num]['duration'] - (ctf_hours, ctf_days) = (str(dur_dict['hours']), str(dur_dict['days'])) - ctf_link = jdata[num]['url'] - ctf_image = jdata[num]['logo'] - ctf_format = jdata[num]['format'] - ctf_place = jdata[num]['onsite'] + for num, i in enumerate(jdata): # Generate list of dicts of upcoming ctfs + ctf_title = jdata[num]["title"] + (ctf_start, ctf_end) = ( + parse(jdata[num]["start"].replace("T", " ").split("+", 1)[0]), + parse(jdata[num]["finish"].replace("T", " ").split("+", 1)[0]), + ) + (unix_start, unix_end) = ( + int(ctf_start.replace(tzinfo=timezone.utc).timestamp()), + int(ctf_end.replace(tzinfo=timezone.utc).timestamp()), + ) + dur_dict = jdata[num]["duration"] + (ctf_hours, ctf_days) = (str(dur_dict["hours"]), str(dur_dict["days"])) + ctf_link = jdata[num]["url"] + ctf_image = jdata[num]["logo"] + ctf_format = jdata[num]["format"] + ctf_place = jdata[num]["onsite"] if ctf_place == False: - ctf_place = 'Online' + ctf_place = "Online" else: - ctf_place = 'Onsite' - + ctf_place = "Onsite" + ctf = { - 'name': ctf_title, - 'start': unix_start, - 'end': unix_end, - 'dur': ctf_days+' days, '+ctf_hours+' hours', - 'url': ctf_link, - 'img': ctf_image, - 'format': ctf_place+' '+ctf_format - } + "name": ctf_title, + "start": unix_start, + "end": unix_end, + "dur": ctf_days + " days, " + ctf_hours + " hours", + "url": ctf_link, + "img": ctf_image, + "format": ctf_place + " " + ctf_format, + } info.append(ctf) - + got_ctfs = [] - for ctf in info: # If the document doesn't exist: add it, if it does: update it. - query = ctf['name'] - ctfs.update({'name': query}, {"$set":ctf}, upsert=True) - got_ctfs.append(ctf['name']) - print(Fore.WHITE + f"{datetime.now()}: " + Fore.GREEN + f"Got and updated {got_ctfs}") + for ( + ctf + ) in info: # If the document doesn't exist: add it, if it does: update it. + query = ctf["name"] + ctfs.update({"name": query}, {"$set": ctf}, upsert=True) + got_ctfs.append(ctf["name"]) + print( + Fore.WHITE + + f"{datetime.now()}: " + + Fore.GREEN + + f"Got and updated {got_ctfs}" + ) print(Style.RESET_ALL) - - - for ctf in ctfs.find(): # Delete ctfs that are over from the db - if ctf['end'] < unix_now: - ctfs.remove({'name': ctf['name']}) + + for ctf in ctfs.find(): # Delete ctfs that are over from the db + if ctf["end"] < unix_now: + ctfs.remove({"name": ctf["name"]}) @updateDB.before_loop async def before_updateDB(self): await self.bot.wait_until_ready() - @commands.group() async def ctftime(self, ctx): - if ctx.invoked_subcommand is None: # If the subcommand passed does not exist, its type is None - ctftime_commands = list(set([c.qualified_name for c in CtfTime.walk_commands(self)][1:])) - await ctx.send(f"Current ctftime commands are: {', '.join(ctftime_commands)}") + ctftime_commands = list( + set([c.qualified_name for c in CtfTime.walk_commands(self)][1:]) + ) + await ctx.send( + f"Current ctftime commands are: {', '.join(ctftime_commands)}" + ) - @ctftime.command(aliases=['now', 'running']) + @ctftime.command(aliases=["now", "running"]) async def current(self, ctx): # Send discord embeds of the currently running ctfs. now = datetime.utcnow() unix_now = int(now.replace(tzinfo=timezone.utc).timestamp()) running = False - + for ctf in ctfs.find(): - if ctf['start'] < unix_now and ctf['end'] > unix_now: # Check if the ctf is running + if ( + ctf["start"] < unix_now and ctf["end"] > unix_now + ): # Check if the ctf is running running = True - embed = discord.Embed(title=':red_circle: ' + ctf['name']+' IS LIVE', description=ctf['url'], color=15874645) - start = datetime.utcfromtimestamp(ctf['start']).strftime('%Y-%m-%d %H:%M:%S') + ' UTC' - end = datetime.utcfromtimestamp(ctf['end']).strftime('%Y-%m-%d %H:%M:%S') + ' UTC' - if ctf['img'] != '': - embed.set_thumbnail(url=ctf['img']) + embed = discord.Embed( + title=":red_circle: " + ctf["name"] + " IS LIVE", + description=ctf["url"], + color=15874645, + ) + start = ( + datetime.utcfromtimestamp(ctf["start"]).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + end = ( + datetime.utcfromtimestamp(ctf["end"]).strftime("%Y-%m-%d %H:%M:%S") + + " UTC" + ) + if ctf["img"] != "": + embed.set_thumbnail(url=ctf["img"]) else: - embed.set_thumbnail(url="https://pbs.twimg.com/profile_images/2189766987/ctftime-logo-avatar_400x400.png") + embed.set_thumbnail( + url="https://pbs.twimg.com/profile_images/2189766987/ctftime-logo-avatar_400x400.png" + ) # CTFtime logo - - embed.add_field(name='Duration', value=ctf['dur'], inline=True) - embed.add_field(name='Format', value=ctf['format'], inline=True) - embed.add_field(name='Timeframe', value=start+' -> '+end, inline=True) + + embed.add_field(name="Duration", value=ctf["dur"], inline=True) + embed.add_field(name="Format", value=ctf["format"], inline=True) + embed.add_field( + name="Timeframe", value=start + " -> " + end, inline=True + ) await ctx.channel.send(embed=embed) - - if running == False: # No ctfs were found to be running - await ctx.send("No CTFs currently running! Check out >ctftime countdown, and >ctftime upcoming to see when ctfs will start!") + + if running == False: # No ctfs were found to be running + await ctx.send( + "No CTFs currently running! Check out >ctftime countdown, and >ctftime upcoming to see when ctfs will start!" + ) @ctftime.command(aliases=["next"]) async def upcoming(self, ctx, amount=None): # Send embeds of upcoming ctfs from ctftime.org, using their api. if not amount: - amount = '3' + amount = "3" headers = { - 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0', + "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0", } upcoming_ep = "https://ctftime.org/api/v1/events/" default_image = "https://pbs.twimg.com/profile_images/2189766987/ctftime-logo-avatar_400x400.png" @@ -137,8 +172,15 @@ async def upcoming(self, ctx, amount=None): for ctf in range(0, int(amount)): ctf_title = upcoming_data[ctf]["title"] - (ctf_start, ctf_end) = (upcoming_data[ctf]["start"].replace("T", " ").split("+", 1)[0] + " UTC", upcoming_data[ctf]["finish"].replace("T", " ").split("+", 1)[0] + " UTC") - (ctf_start, ctf_end) = (re.sub(":00 ", " ", ctf_start), re.sub(":00 ", " ", ctf_end)) + (ctf_start, ctf_end) = ( + upcoming_data[ctf]["start"].replace("T", " ").split("+", 1)[0] + " UTC", + upcoming_data[ctf]["finish"].replace("T", " ").split("+", 1)[0] + + " UTC", + ) + (ctf_start, ctf_end) = ( + re.sub(":00 ", " ", ctf_start), + re.sub(":00 ", " ", ctf_end), + ) dur_dict = upcoming_data[ctf]["duration"] (ctf_hours, ctf_days) = (str(dur_dict["hours"]), str(dur_dict["days"])) ctf_link = upcoming_data[ctf]["url"] @@ -150,40 +192,52 @@ async def upcoming(self, ctx, amount=None): else: ctf_place = "Onsite" - embed = discord.Embed(title=ctf_title, description=ctf_link, color=int("f23a55", 16)) - if ctf_image != '': + embed = discord.Embed( + title=ctf_title, description=ctf_link, color=int("f23a55", 16) + ) + if ctf_image != "": embed.set_thumbnail(url=ctf_image) else: embed.set_thumbnail(url=default_image) - embed.add_field(name="Duration", value=((ctf_days + " days, ") + ctf_hours) + " hours", inline=True) - embed.add_field(name="Format", value=(ctf_place + " ") + ctf_format, inline=True) - embed.add_field(name="Timeframe", value=(ctf_start + " -> ") + ctf_end, inline=True) + embed.add_field( + name="Duration", + value=((ctf_days + " days, ") + ctf_hours) + " hours", + inline=True, + ) + embed.add_field( + name="Format", value=(ctf_place + " ") + ctf_format, inline=True + ) + embed.add_field( + name="Timeframe", value=(ctf_start + " -> ") + ctf_end, inline=True + ) await ctx.channel.send(embed=embed) - + @ctftime.command(aliases=["leaderboard"]) - async def top(self, ctx, year = None): + async def top(self, ctx, year=None): # Send a message of the ctftime.org leaderboards from a supplied year (defaults to current year). - + if not year: # Default to current year year = str(datetime.today().year) headers = { - 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0', + "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0", } top_ep = f"https://ctftime.org/api/v1/top/{year}/" leaderboards = "" r = requests.get(top_ep, headers=headers) if r.status_code != 200: - await ctx.send("Error retrieving data, please report this with `>report \"what happened\"`") + await ctx.send( + 'Error retrieving data, please report this with `>report "what happened"`' + ) else: try: top_data = (r.json())[year] for team in range(10): # Leaderboard is always top 10 so we can just assume this for ease of formatting rank = team + 1 - teamname = top_data[team]['team_name'] - score = str(round(top_data[team]['points'], 4)) + teamname = top_data[team]["team_name"] + score = str(round(top_data[team]["points"], 4)) if team != 9: # This is literally just for formatting. I'm sure there's a better way to do it but I couldn't think of one :( @@ -192,10 +246,13 @@ async def top(self, ctx, year = None): else: leaderboards += f"\n[{rank}] {teamname}: {score}\n" - await ctx.send(f":triangular_flag_on_post: **{year} CTFtime Leaderboards**```ini\n{leaderboards}```") + await ctx.send( + f":triangular_flag_on_post: **{year} CTFtime Leaderboards**```ini\n{leaderboards}```" + ) except KeyError as e: await ctx.send("Please supply a valid year.") # LOG THIS + @ctftime.command() async def timeleft(self, ctx): # Send the specific time that ctfs that are currently running have left. @@ -203,9 +260,11 @@ async def timeleft(self, ctx): unix_now = int(now.replace(tzinfo=timezone.utc).timestamp()) running = False for ctf in ctfs.find(): - if ctf['start'] < unix_now and ctf['end'] > unix_now: # Check if the ctf is running + if ( + ctf["start"] < unix_now and ctf["end"] > unix_now + ): # Check if the ctf is running running = True - time = ctf['end'] - unix_now + time = ctf["end"] - unix_now days = time // (24 * 3600) time = time % (24 * 3600) hours = time // 3600 @@ -213,35 +272,51 @@ async def timeleft(self, ctx): minutes = time // 60 time %= 60 seconds = time - await ctx.send(f"```ini\n{ctf['name']} ends in: [{days} days], [{hours} hours], [{minutes} minutes], [{seconds} seconds]```\n{ctf['url']}") - + await ctx.send( + f"```ini\n{ctf['name']} ends in: [{days} days], [{hours} hours], [{minutes} minutes], [{seconds} seconds]```\n{ctf['url']}" + ) + if running == False: - await ctx.send('No ctfs are running! Use >ctftime upcoming or >ctftime countdown to see upcoming ctfs') + await ctx.send( + "No ctfs are running! Use >ctftime upcoming or >ctftime countdown to see upcoming ctfs" + ) @ctftime.command() async def countdown(self, ctx, params=None): # Send the specific time that upcoming ctfs have until they start. now = datetime.utcnow() unix_now = int(now.replace(tzinfo=timezone.utc).timestamp()) - + if params == None: self.upcoming_l = [] index = "" for ctf in ctfs.find(): - if ctf['start'] > unix_now: + if ctf["start"] > unix_now: # if the ctf start time is in the future... self.upcoming_l.append(ctf) for i, c in enumerate(self.upcoming_l): index += f"\n[{i + 1}] {c['name']}\n" - - await ctx.send(f"Type >ctftime countdown to select.\n```ini\n{index}```") + + await ctx.send( + f"Type >ctftime countdown to select.\n```ini\n{index}```" + ) else: if self.upcoming_l != []: - x = int(params) - 1 - start = datetime.utcfromtimestamp(self.upcoming_l[x]['start']).strftime('%Y-%m-%d %H:%M:%S') + ' UTC' - end = datetime.utcfromtimestamp(self.upcoming_l[x]['end']).strftime('%Y-%m-%d %H:%M:%S') + ' UTC' - - time = self.upcoming_l[x]['start'] - unix_now + x = int(params) - 1 + start = ( + datetime.utcfromtimestamp(self.upcoming_l[x]["start"]).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + end = ( + datetime.utcfromtimestamp(self.upcoming_l[x]["end"]).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + + time = self.upcoming_l[x]["start"] - unix_now days = time // (24 * 3600) time = time % (24 * 3600) hours = time // 3600 @@ -249,17 +324,29 @@ async def countdown(self, ctx, params=None): minutes = time // 60 time %= 60 seconds = time - - await ctx.send(f"```ini\n{self.upcoming_l[x]['name']} starts in: [{days} days], [{hours} hours], [{minutes} minutes], [{seconds} seconds]```\n{self.upcoming_l[x]['url']}") - else: # TODO: make this a function, too much repeated code here. + + await ctx.send( + f"```ini\n{self.upcoming_l[x]['name']} starts in: [{days} days], [{hours} hours], [{minutes} minutes], [{seconds} seconds]```\n{self.upcoming_l[x]['url']}" + ) + else: # TODO: make this a function, too much repeated code here. for ctf in ctfs.find(): - if ctf['start'] > unix_now: + if ctf["start"] > unix_now: self.upcoming_l.append(ctf) - x = int(params) - 1 - start = datetime.utcfromtimestamp(self.upcoming_l[x]['start']).strftime('%Y-%m-%d %H:%M:%S') + ' UTC' - end = datetime.utcfromtimestamp(self.upcoming_l[x]['end']).strftime('%Y-%m-%d %H:%M:%S') + ' UTC' - - time = self.upcoming_l[x]['start'] - unix_now + x = int(params) - 1 + start = ( + datetime.utcfromtimestamp(self.upcoming_l[x]["start"]).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + end = ( + datetime.utcfromtimestamp(self.upcoming_l[x]["end"]).strftime( + "%Y-%m-%d %H:%M:%S" + ) + + " UTC" + ) + + time = self.upcoming_l[x]["start"] - unix_now days = time // (24 * 3600) time = time % (24 * 3600) hours = time // 3600 @@ -267,8 +354,11 @@ async def countdown(self, ctx, params=None): minutes = time // 60 time %= 60 seconds = time - - await ctx.send(f"```ini\n{self.upcoming_l[x]['name']} starts in: [{days} days], [{hours} hours], [{minutes} minutes], [{seconds} seconds]```\n{self.upcoming_l[x]['url']}") -def setup(bot): - bot.add_cog(CtfTime(bot)) + await ctx.send( + f"```ini\n{self.upcoming_l[x]['name']} starts in: [{days} days], [{hours} hours], [{minutes} minutes], [{seconds} seconds]```\n{self.upcoming_l[x]['url']}" + ) + + +async def setup(bot): + await bot.add_cog(CtfTime(bot)) diff --git a/cogs/encoding.py b/cogs/encoding.py index 34061c6..5ba35d2 100644 --- a/cogs/encoding.py +++ b/cogs/encoding.py @@ -9,10 +9,10 @@ # Encoding/Decoding from various schemes. -#TODO: l14ck3r0x01: ROT47 , base32 encoding +# TODO: l14ck3r0x01: ROT47 , base32 encoding -class Encoding(commands.Cog): +class Encoding(commands.Cog): def __init__(self, bot): self.bot = bot @@ -22,63 +22,64 @@ async def cog_command_error(self, ctx, error): @commands.command() async def b64(self, ctx, encode_or_decode, string): byted_str = str.encode(string) - - if encode_or_decode == 'decode': - decoded = base64.b64decode(byted_str).decode('utf-8') + + if encode_or_decode == "decode": + decoded = base64.b64decode(byted_str).decode("utf-8") await ctx.send(decoded) - - if encode_or_decode == 'encode': - encoded = base64.b64encode(byted_str).decode('utf-8').replace('\n', '') + + if encode_or_decode == "encode": + encoded = base64.b64encode(byted_str).decode("utf-8").replace("\n", "") await ctx.send(encoded) - + @commands.command() async def b32(self, ctx, encode_or_decode, string): byted_str = str.encode(string) - if encode_or_decode == 'decode': - decoded = base64.b32decode(byted_str).decode('utf-8') + if encode_or_decode == "decode": + decoded = base64.b32decode(byted_str).decode("utf-8") await ctx.send(decoded) - - if encode_or_decode =='encode': - encoded = base64.b32encode(byted_str).decode('utf-8').replace('\n', '') + + if encode_or_decode == "encode": + encoded = base64.b32encode(byted_str).decode("utf-8").replace("\n", "") await ctx.send(encoded) @commands.command() async def binary(self, ctx, encode_or_decode, string): - if encode_or_decode == 'decode': + if encode_or_decode == "decode": string = string.replace(" ", "") data = int(string, 2) - decoded = data.to_bytes((data.bit_length() + 7) // 8, 'big').decode() + decoded = data.to_bytes((data.bit_length() + 7) // 8, "big").decode() await ctx.send(decoded) - - if encode_or_decode == 'encode': - encoded = bin(int.from_bytes(string.encode(), 'big')).replace('b', '') + + if encode_or_decode == "encode": + encoded = bin(int.from_bytes(string.encode(), "big")).replace("b", "") await ctx.send(encoded) @commands.command() async def hex(self, ctx, encode_or_decode, string): - if encode_or_decode == 'decode': + if encode_or_decode == "decode": string = string.replace(" ", "") - decoded = binascii.unhexlify(string).decode('ascii') + decoded = binascii.unhexlify(string).decode("ascii") await ctx.send(decoded) - - if encode_or_decode == 'encode': + + if encode_or_decode == "encode": byted = string.encode() - encoded = binascii.hexlify(byted).decode('ascii') + encoded = binascii.hexlify(byted).decode("ascii") await ctx.send(encoded) @commands.command() async def url(self, ctx, encode_or_decode, message): - if encode_or_decode == 'decode': - - if '%20' in message: - message = message.replace('%20', '(space)') + if encode_or_decode == "decode": + if "%20" in message: + message = message.replace("%20", "(space)") await ctx.send(urllib.parse.unquote(message)) else: await ctx.send(urllib.parse.unquote(message)) - - if encode_or_decode == 'encode': + + if encode_or_decode == "encode": await ctx.send(urllib.parse.quote(message)) -def setup(bot): - bot.add_cog(Encoding(bot)) \ No newline at end of file + +async def setup(bot): + await bot.add_cog(Encoding(bot)) + diff --git a/cogs/utility.py b/cogs/utility.py index f110471..70ca08d 100644 --- a/cogs/utility.py +++ b/cogs/utility.py @@ -8,66 +8,72 @@ # This can be thought of as a miscellaneous category (anything 'utility' based.) -class Utility(commands.Cog): +class Utility(commands.Cog): def __init__(self, bot): self.bot = bot - @commands.command(aliases=['char']) + @commands.command(aliases=["char"]) async def characters(self, ctx, string): await ctx.send(len(string)) - @commands.command(aliases=['wc']) + @commands.command(aliases=["wc"]) async def wordcount(self, ctx, *args): await ctx.send(len(args)) - @commands.command(aliases=['rev']) + @commands.command(aliases=["rev"]) async def reverse(self, ctx, message): - await ctx.send(message[::(- 1)]) + await ctx.send(message[::(-1)]) @commands.command() async def counteach(self, ctx, message): # Count the amount of characters in a string. count = {} - + for char in message: if char in count.keys(): count[char] += 1 else: count[char] = 1 - + await ctx.send(str(count)) - @commands.command(aliases=['head']) + @commands.command(aliases=["head"]) async def magicb(self, ctx, filetype): # Get the magic bytes from a filetype - file = open('magic.json').read() + file = open("magic.json").read() alldata = json.loads(file) try: - messy_signs = str(alldata[filetype]['signs']) - signs = messy_signs.split('[')[1].split(',')[0].split(']')[0].replace("'", '') - filetype = alldata[filetype]['mime'] - await ctx.send(f'''{filetype}: {signs}''') - except: # if the filetype is not in magicb.json... - await ctx.send(f"{filetype} not found :( If you think this filetype should be included please do `>request \"magicb {filetype}\"`") + messy_signs = str(alldata[filetype]["signs"]) + signs = ( + messy_signs.split("[")[1].split(",")[0].split("]")[0].replace("'", "") + ) + filetype = alldata[filetype]["mime"] + await ctx.send(f"""{filetype}: {signs}""") + except: # if the filetype is not in magicb.json... + await ctx.send( + f'{filetype} not found :( If you think this filetype should be included please do `>request "magicb {filetype}"`' + ) @commands.command() async def twitter(self, ctx, twituser): - await ctx.send('https://twitter.com/' + twituser) + await ctx.send("https://twitter.com/" + twituser) @commands.command() async def github(self, ctx, gituser): - await ctx.send('https://github.com/' + gituser) + await ctx.send("https://github.com/" + gituser) - @commands.command(aliases=['5050', 'flip']) + @commands.command(aliases=["5050", "flip"]) async def cointoss(self, ctx): choice = random.randint(1, 2) - + if choice == 1: - await ctx.send('heads') - + await ctx.send("heads") + if choice == 2: - await ctx.send('tails') + await ctx.send("tails") + + +async def setup(bot): + await bot.add_cog(Utility(bot)) -def setup(bot): - bot.add_cog(Utility(bot)) \ No newline at end of file diff --git a/nullctf.py b/nullctf.py index 0085660..6c119bb 100644 --- a/nullctf.py +++ b/nullctf.py @@ -3,7 +3,7 @@ from discord.ext import commands import os import sys - +import asyncio import help_info import config_vars @@ -143,11 +143,15 @@ async def amicool(ctx): ) -if __name__ == "__main__": +async def setup(): sys.path.insert(1, os.getcwd() + "/cogs/") for extension in extensions: try: - bot.load_extension(extension) + await bot.load_extension(extension) except Exception as e: print(f"Failed to load cogs : {e}") + + +if __name__ == "__main__": + asyncio.run(setup()) bot.run(config_vars.discord_token) diff --git a/pyproject.toml b/pyproject.toml index 6b424a4..d441e05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,9 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.10" dependencies = [ + "colorama>=0.4.6", "discord>=2.3.2", "pymongo>=4.10.1", + "python-dateutil>=2.9.0.post0", + "requests>=2.32.3", ]