Skip to content
Open
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# fiskebot
![](images/fiskebot_logo.png)
# Zookeeper

This bot is still work in process. It is a fork of [eptbot](https://github.com/ept-team/eptbot), which is again a fork of [igCTF](https://gitlab.com/inequationgroup/igCTF), which is again a fork of [NullCTF](https://github.com/NullPxl/NullCTF).
This bot is still work in process. It is a fork of [fiskebot](https://github.com/dugnadctf/fiskebot/), which is again a fork of [eptbot](https://github.com/ept-team/eptbot), which is again a fork of [igCTF](https://gitlab.com/inequationgroup/igCTF), which is again a fork of [NullCTF](https://github.com/NullPxl/NullCTF).

## Install

Expand All @@ -26,9 +25,10 @@ The only required variable is `DISCORD_TOKEN`, the rest will use the default val
| `CATEGORY_ARCHIVE_PREFIX` | `archive` | Category to move channels to when the CTF is over. There is a max limit on 50 channels per category. The bot wil automatically move channels to new categories when needed |
| `CHANNEL_EXPORT` | `export` | The channel to upload exports to |
| `CHANNEL_LOGGING_ID` | | If enabled, will send logging to this channel, based on the `LOGGING_DISCORD_LEVEL` logging level |
| `CHANNEL_NAME_DELIMITER` | `-` | The delimiter for the channel names, must be one of `-` or `_`. Results in `-`: `#ctf-challenge-name`, and `_`: `#ctf_challenge_name` |
| `CHANNEL_NAME_DELIMITER` | ` ` | The delimiter for the channel names, must be one of `-`, or `_`. Results in `-`: `#ctf-challenge-name`, and `_`: `#ctf_challenge_name` |
| `CTFTIME_TEAM_ID` | | CTFtime ID for the `!ctftime team` command |
| `CTFTIME_TEAM_NAME` | | CTFtime name for the `!ctftime team` command |
| `REACT_FOR_CHALLENGE` | `False` | If participants has to react to message to join challenge-threads |

### start

Expand Down Expand Up @@ -77,10 +77,10 @@ $ cp git-hook .git/hooks/pre-commit
- `!create "ctf name"` This is the command you'll use when you want to begin a new CTF. This command will make a text channel with your supplied name. The bot will also send a message in chat where members can react to join the CTF.
![enter image description here](images/ept-create.PNG)

- `!add <challenge name>` This will create a new channel for a given challenge.
![enter image description here](images/ept-add.PNG)
- `!done [@users ...]` Mark a challenge as done. Needs to be done inside the challenge channel. Optionally specify other users who also contributed to solving the challenge, space separated without the @s.
![enter image description here](images/ept-done.PNG)
- `!add <challenge name>` This will create a new thread for a given challenge.

- `!done [@users ...]` Mark a challenge as done. Needs to be done inside the challenge thread. Optionally specify other users who also contributed to solving the challenge, space separated without the @s.

- `!ctf archive` Mark the ctf as over and move it to the archive categories (specified in `/bot/config.py`).

---
Expand All @@ -102,3 +102,5 @@ $ cp git-hook .git/hooks/pre-commit
![enter image description here](images/ctftime-team.png)

> ### Have a feature request? Make a GitHub issue.

> Please upvote this feature request https://github.com/discord/discord-api-docs/discussions/6084
16 changes: 16 additions & 0 deletions bot/bot.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
2023-05-02 21:12:04,524:INFO:bot: <botTesting Online>
2023-05-02 21:12:04,524:INFO:bot: discord.py 2.1.0
2023-05-02 21:14:19,752:ERROR:bot: Exception in command delete
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/discord/ext/commands/bot.py", line 1349, in invoke
await ctx.command.invoke(ctx)
File "/usr/local/lib/python3.9/site-packages/discord/ext/commands/core.py", line 1015, in invoke
await self.prepare(ctx)
File "/usr/local/lib/python3.9/site-packages/discord/ext/commands/core.py", line 932, in prepare
await self._parse_arguments(ctx)
File "/usr/local/lib/python3.9/site-packages/discord/ext/commands/core.py", line 839, in _parse_arguments
transformed = await self.transform(ctx, param, attachments)
File "/usr/local/lib/python3.9/site-packages/discord/ext/commands/core.py", line 691, in transform
raise MissingRequiredArgument(param)
discord.ext.commands.errors.MissingRequiredArgument: name is a required argument that is missing.

141 changes: 97 additions & 44 deletions bot/bot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/usr/bin/env python3
import asyncio
import logging
import os.path
Expand All @@ -15,14 +16,28 @@
from helpers import helpers
from logger import BotLogger

# TODO: Append channel id to naming in db to fix naming conflicts. For both teams and challenges
# Possible naming convention:
# f"{name}-{ctx.channel.id}"

logger = BotLogger("bot")

if not config["token"]:
logger.error("DISCORD_TOKEN has not been set")
exit(1)

client = discord.Client()
bot = commands.Bot(command_prefix=config["prefix"])
#Need to figure out which intents is needed
intents = discord.Intents.all()
intents.members = True
intents.typing = True
intents.presences = True
intents.messages = True
intents.reactions = True

client = discord.Client(intents=intents)
bot = commands.Bot(command_prefix=config["prefix"],intents=intents)



intents = discord.Intents.default()
intents.members = True
Expand Down Expand Up @@ -89,46 +104,69 @@ async def on_raw_reaction_add(payload):
guild = bot.get_guild(payload.guild_id)
chan = bot.get_channel(payload.channel_id)
team = db.teamdb[str(payload.guild_id)].find_one({"msg_id": payload.message_id})
challenge = db.challdb[str(payload.guild_id)].find_one({"msg_id": payload.message_id})
member = await guild.fetch_member(payload.user_id)
if guild and member and chan:
# logger.debug(f"Added reaction: {payload}")
# logger.debug(f"Guild: {guild}, Channel: {chan}, Team: {team}, Member: {member}")
if not team:
# logger.error(f"Not adding role. Could find team")
return
role = guild.get_role(team["role_id"])
if not role:
logger.error(
f"Not adding role. Could not find role ID {team['role_id']} in Discord"

if guild and member and chan and not member.bot:
if team:
role = guild.get_role(team["role_id"])
if not role:
logger.error(
f"Not adding role. Could not find role ID {team['role_id']} in Discord"
)
logger.error(team)
return

await member.add_roles(role, reason="User wanted to join team")
elif challenge and config['react_for_challenge']:
# logger.debug(f"Adding {member.name} to thread")
thread = guild.get_thread(challenge['thread_id'])
await thread.add_user(member)
db.challdb[str(payload.guild_id)].update_one(
{"msg_id": payload.message_id}, {"$push": {"working": member.id}}
)
logger.error(team)
# logger.debug(f"Added {member.name} to thread")
else:
# logger.error(f"Not adding role. Could find team")
return

await member.add_roles(role, reason="User wanted to join team")
logger.debug(f"Added role {role} to user {member}")


@bot.event
async def on_raw_reaction_remove(payload):
# check if the user is not the bot
guild = bot.get_guild(payload.guild_id)
team = db.teamdb[str(payload.guild_id)].find_one({"msg_id": payload.message_id})
challenge = db.challdb[str(payload.guild_id)].find_one({"msg_id": payload.message_id})
member = await guild.fetch_member(payload.user_id)
if guild and member:

if guild and member and not member.bot:
# logger.debug(f"Removed reaction: {payload}")
# logger.debug(f"Guild: {guild}, Team: {team}, Member: {member}")
if not team:

if team:
role = guild.get_role(team["role_id"])
if not role:
logger.error(f"Not removing role. Could not find role ID {team['role_id']}")
logger.error(team)
return
await member.remove_roles(role, reason="User wanted to leave team")
# logger.debug(f"Removed role {role} from user {member}")

elif challenge and config['react_for_challenge']:
# logger.debug(f"Removing {member.name} to thread")
thread = guild.get_thread(challenge['thread_id'])
await thread.remove_user(member)
db.challdb[str(payload.guild_id)].update_one(
{"msg_id": payload.message_id}, {"$pull": {"working": member.id}}
)
# logger.debug(f"Removed {member.name} to thread")
else:
# logger.error(f"Not removing role. Could find team")
return
role = guild.get_role(team["role_id"])
if not role:
logger.error(f"Not removing role. Could not find role ID {team['role_id']}")
logger.error(team)
return
await member.remove_roles(role, reason="User wanted to leave team")
logger.debug(f"Removed role {role} from user {member}")





async def embed_help(chan, help_topic, help_text):
emb = discord.Embed(description=help_text, colour=4387968)
emb.set_author(name=help_topic)
Expand All @@ -154,26 +192,31 @@ async def help(ctx, category=None):

@bot.command()
async def request(ctx, feature):
for cid in config["maintainers"]:
creator = bot.get_user(cid)
authors_name = str(ctx.author)
await creator.send(f""":pencil: {authors_name}: {feature}""")
await ctx.send(f""":pencil: Thanks, "{feature}" has been requested!""")
if config["maintainers"]:
for cid in config["maintainers"]:
creator = bot.get_user(cid)
authors_name = str(ctx.author)
await creator.send(f""":pencil: {authors_name}: {feature}""")
await ctx.send(f""":pencil: Thanks, "{feature}" has been requested!""")
else:
await ctx.send(f""":pencil: No maintainers listed in config!""")


@bot.command()
async def report(ctx, error_report):
for cid in config["maintainers"]:
creator = bot.get_user(cid)
authors_name = str(ctx.author)
await creator.send(
f""":triangular_flag_on_post: {authors_name}: {error_report}"""
if config["maintainers"]:
for cid in config["maintainers"]:
creator = bot.get_user(cid)
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 ctx.send(
f""":triangular_flag_on_post: Thanks for the help, "{error_report}" has been reported!"""
)


else:
await ctx.send(f""":pencil: No maintainers listed in config!""")

@bot.command()
async def setup(ctx):
if ctx.author.id not in config["maintainers"]:
Expand Down Expand Up @@ -224,7 +267,17 @@ async def exit(ctx):

# -------------------

async def loadExtras(bot):
await bot.load_extension("ctftime")
await bot.load_extension("ctfs")

async def main():

await loadExtras(bot)

async with bot:
await bot.start(config["token"])


if __name__ == "__main__":
bot.load_extension("ctftime")
bot.load_extension("ctfs")
bot.run(config["token"])
asyncio.run(main())
6 changes: 4 additions & 2 deletions bot/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import os


def parse_variable(variable, default=None, valid=None):
value = os.getenv(variable, None)
if default and valid and variable not in valid:
Expand Down Expand Up @@ -76,11 +75,14 @@ def parse_int_list(variable):
},
# The delimiter for the channel names, must be one of "-" or "_". i.e. "-": "#ctf-challenge-name", "_": "#ctf_challenge_name"
"challenge_name_delimiter": parse_variable(
"CHALLENGE_NAME_DELIMITER", "-", valid=["-", "_"]
"CHALLENGE_NAME_DELIMITER", " ", valid=["-", "_", " "]
),
# CTFtime id for the default team to lookup using the `!ctftime team` command
"team": {
"id": parse_variable("CTFTIME_TEAM_ID", -1),
"name": parse_variable("CTFTIME_TEAM_NAME"),
},
# If enabled, users must react to message to make bot add them to thread. Should be visible in threads list anyways.
# Should be True if numbers of participants is high to reduce resourceuse on bot and spam in threads
"react_for_challenge": parse_variable("REACT_FOR_CHALLENGE", False)
}
3 changes: 2 additions & 1 deletion bot/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
SOURCE_FORK1 = "https://github.com/NullPxl/NullCTF"
SOURCE_FORK2 = "https://gitlab.com/inequationgroup/igCTF"
SOURCE_FORK3 = "https://github.com/ept-team/eptbot"
SOURCES_TEXT = f"Source: https://github.com/ekofiskctf/fiskebot\nForked from: {SOURCE_FORK3}\nWho again forked from: {SOURCE_FORK2}\nWho again forked from: {SOURCE_FORK1}"
SOURCE_FORK4 = "https://github.com/ekofiskctf/fiskebot"
SOURCES_TEXT = f"Source: https://github.com/RumbleJungleCTF/RumbleBot\nForked from: {SOURCE_FORK4}\nWho again forked from: {SOURCE_FORK3}\nWho again forked from: {SOURCE_FORK2}\nWho again forked from: {SOURCE_FORK1}"
Loading