Skip to content

Commit

Permalink
Merge branch 'pwncollege:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
mudongliang authored Jan 10, 2024
2 parents 4a03b7d + 32baac3 commit 1ecdd2f
Show file tree
Hide file tree
Showing 25 changed files with 487 additions and 199 deletions.
2 changes: 1 addition & 1 deletion ctfd/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ gunicorn==20.1.0
dataset==1.5.2
cmarkgfm==2022.10.27
redis==4.5.5
gevent==23.9.0
gevent==23.9.1
python-dotenv==0.13.0
flask-restx==1.1.0
flask-marshmallow==0.10.1
Expand Down
164 changes: 90 additions & 74 deletions discord_bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
from enum import Enum

import discord
import pandas as pd
from sqlalchemy import create_engine


DISCORD_BOT_TOKEN = os.environ["DISCORD_BOT_TOKEN"]
if not DISCORD_BOT_TOKEN:
Expand Down Expand Up @@ -40,6 +37,8 @@ async def setup_hook(self):


daily_tasks = set()


def run_daily(task, daily_time):
time = datetime.time.fromisoformat(daily_time)
now = datetime.datetime.now(tz=time.tzinfo)
Expand Down Expand Up @@ -76,7 +75,11 @@ async def on_ready():
if channel.category and channel.category.name.lower() == "logs" and channel.name == "attendance")
client.voice_state_history = collections.defaultdict(list)
run_daily(daily_attendance, "17:20:00-07:00")
run_daily(check_belts, "18:00:00-07:00")

client.emoji = collections.defaultdict()
for emo in client.emojis:
client.emoji[emo.name] = emo


@client.event
async def on_voice_state_update(member, before, after):
Expand All @@ -96,7 +99,8 @@ async def show_join_date(interaction: discord.Interaction, member: discord.Membe
await interaction.response.send_message(f"{member.mention} joined at {discord.utils.format_dt(member.joined_at)}", ephemeral=True)


async def send_logged_embed(interaction, message, log_channel, title, logged_text, ephemeral_text, button_text):
async def send_logged_embed(interaction, message, log_channel, title, logged_text, ephemeral_text, button_text, emoji="\N{Upwards Black Arrow}"):
emoji = "\N{Upwards Black Arrow}" if emoji is None else emoji
now = datetime.datetime.utcnow()

logged_embed = discord.Embed(title=title)
Expand All @@ -121,7 +125,7 @@ async def send_logged_embed(interaction, message, log_channel, title, logged_tex

await interaction.response.send_message(embed=ephemeral_embed, view=ephemeral_url_view, ephemeral=True)

await message.add_reaction("\N{Upwards Black Arrow}")
await message.add_reaction(emoji)


@client.tree.context_menu(name="Thanks")
Expand All @@ -130,13 +134,18 @@ async def thank_message(interaction: discord.Interaction, message: discord.Messa
await interaction.response.send_message("You cannot thank yourself!", ephemeral=True)
return

if interaction.channel.name == "memes":
await interaction.response.send_message("You cannot thank a meme!", ephemeral=True)
return

await send_logged_embed(interaction,
message,
client.thanks_log_channel,
title="Thanks",
logged_text=f"{interaction.user.mention} thanked {message.author.mention}",
ephemeral_text=f"You thanked {message.author.mention}",
button_text="Thanks Message")
button_text="Thanks Message",
emoji=client.emoji["thanks"])

print(f"{describe(interaction.user)} thanked {describe(message.author)}", flush=True)

Expand All @@ -155,11 +164,68 @@ async def like_meme(interaction: discord.Interaction, message: discord.Message):
title="Liked Meme",
logged_text=f"{interaction.user.mention} liked {message.author.mention}'s meme",
ephemeral_text=f"You liked {message.author.mention}'s meme",
button_text="Liked Meme")
button_text="Liked Meme",
emoji=client.emoji["good_meme"])

print(f"{describe(interaction.user)} liked {describe(message.author)}'s meme")


async def thread_forum_checkwarn(interaction, message):
thread = message.channel
if not isinstance(thread, discord.Thread):
await interaction.response.send_message("Only forum threads can be tagged!", ephemeral=True)
return (None, None)

forum = thread.parent
if not isinstance(forum, discord.ForumChannel):
await interaction.response.send_message("Only forum threads can be tagged!", ephemeral=True)
return (None, None)

return (thread, forum)


async def is_any_sensei_checkwarn(interaction):
senseis = list(role for role in interaction.guild.roles if "sensei" in role.name.lower())

if any(interaction.user in sensei.members for sensei in senseis):
return True

await interaction.response.send_message("You are not a sensei!", ephemeral=True)
return False


@client.tree.context_menu(name="Tag: Good Question")
async def good_question(interaction: discord.Interaction, message: discord.Message):
if not await is_any_sensei_checkwarn(interaction):
return

thread, forum = await thread_forum_checkwarn(interaction, message)

if thread is None or forum is None:
return

good_q_tag = next(tag for tag in forum.available_tags if tag.name=="Good Question")

await thread.add_tags(good_q_tag)
await interaction.response.send_message("Tagged: Good Question", ephemeral=True)


@client.tree.context_menu(name="Tag: Resolved")
async def resolve_thread(interaction: discord.Interaction, message: discord.Message):
thread, forum = await thread_forum_checkwarn(interaction, message)
if thread is None or forum is None:
return

if interaction.user != thread.owner:
await interaction.response.send_message("Only OP can resolve a thread!", ephemeral=True)
return

resolved_tag = next(tag for tag in forum.available_tags if tag.name=="Resolved")

await thread.add_tags(resolved_tag)
await interaction.response.send_message("Tagged: Resolved", ephemeral=True)


async def mark_attendance(member):
now = datetime.datetime.utcnow()

Expand All @@ -175,10 +241,7 @@ async def mark_attendance(member):

@client.tree.command()
async def attend(interaction: discord.Interaction, member: discord.Member):
senseis = list(role for role in interaction.guild.roles if "sensei" in role.name.lower())

if not any(interaction.user in sensei.members for sensei in senseis):
await interaction.response.send_message("You are not a sensei!", ephemeral=True)
if not await is_any_sensei_checkwarn(interaction):
return

now = datetime.datetime.utcnow()
Expand All @@ -195,7 +258,6 @@ async def attend(interaction: discord.Interaction, member: discord.Member):
ephemeral_url_view.add_item(discord.ui.Button(label="Attendance", style=discord.ButtonStyle.url, url=logged_message.jump_url))

await interaction.response.send_message(embed=ephemeral_embed, view=ephemeral_url_view, ephemeral=True)

print(f"{describe(interaction.user)} attended {member}")


Expand Down Expand Up @@ -243,69 +305,23 @@ async def help(interaction: discord.Interaction):

ephemeral_url_view = discord.ui.View()
ephemeral_url_view.add_item(discord.ui.Button(label="Private Help Thread", style=discord.ButtonStyle.url, url=thread.jump_url))

await interaction.response.send_message(view=ephemeral_url_view, ephemeral=True)


async def check_belts():
now = datetime.datetime.now()
print(f"Checking belts @ {now}")

class Belt(Enum):
ORANGE = 0
YELLOW = 1
BLUE = 2

engine = create_engine('mariadb+pymysql://ctfd:ctfd@db/ctfd?charset=utf8mb4')

async def check_belts(rank: Belt):
roles = [next(role for role in client.guild.roles if role.name == "Orange Belt"),
next(role for role in client.guild.roles if role.name == "Yellow Belt"),
next(role for role in client.guild.roles if role.name == "Blue Belt")]

courses = [62725971, # CSE 365 - Spring 2023
-2037203363, # CSE 466 - Fall 2022
-695929874 # CSE 494 - Spring 2023
]

completion_cnts = [167, # CSE 365 - Spring 2023
355, # CSE 466 - Fall 2022
160 # CSE 494 - Spring 2023
]

role = roles[rank.value]
course = courses[rank.value]
completion = completion_cnts[rank.value]

# TODO: Hardcoded Challenge Max
belted = pd.read_sql(f'''
SELECT u.name, dis.discord_id, count(s.challenge_id)
FROM dojo_challenges as d
JOIN solves as s
ON d.challenge_id=s.challenge_id
JOIN users as u
ON u.id=s.user_id
JOIN discord_users as dis
ON u.id=dis.user_id
WHERE d.dojo_id={course}
GROUP BY u.name
HAVING count(s.challenge_id)={completion};
''', engine)

new_belt_cnt = 0
for _, row in belted.iterrows():
try:
member = await client.guild.fetch_member(row['discord_id'])
except:
continue
if role not in member.roles:
now = datetime.datetime.now()
print(f"Awarding {role.name} to {member.display_name} @ {now}")
new_belt_cnt += 1
await member.add_roles(role)

await check_belts(Belt.ORANGE)
await check_belts(Belt.YELLOW)
await check_belts(Belt.BLUE)
@client.event
async def on_reaction_add(reaction, user):
if isinstance(reaction.emoji, str):
return

# Bot must thank first or else emoji is removed
bot_thanked = [u async for u in reaction.users() if u == client.guild.me]
if reaction.emoji.name == "thanks" and not bot_thanked:
await reaction.remove(user)

# Bot must meme first or else emoji is removed
bot_memed = [u async for u in reaction.users() if u == client.guild.me]
meme_chan = reaction.message.channel.name == "memes"
if reaction.emoji.name == "good_meme" and meme_chan and not bot_memed:
await reaction.remove(user)

client.run(DISCORD_BOT_TOKEN)
5 changes: 2 additions & 3 deletions dojo_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,8 @@ def solve(cls, user, team, challenge, request):
user_mention = f"<@{discord_user['user']['id']}>"
message = f"{user_mention} earned their {belt}! :tada:"
print(message, flush=True)
# TODO: Discord instead of print
# add_role(discord_user["user"]["id"], belt)
# send_message(message, "belting-ceremony")
add_role(discord_user["user"]["id"], belt)
send_message(message, "belting-ceremony")


class DojoFlag(BaseFlag):
Expand Down
31 changes: 30 additions & 1 deletion dojo_plugin/api/v1/dojo.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from CTFd.utils.security.sanitize import sanitize_html

from ...models import Dojos, DojoMembers, DojoAdmins
from ...utils.dojo import dojo_accessible, dojo_clone, load_dojo_dir
from ...utils.dojo import dojo_accessible, dojo_clone, load_dojo_dir, dojo_route


dojo_namespace = Namespace(
Expand Down Expand Up @@ -88,3 +88,32 @@ def post(self):
private_key = data.get("private_key", "").replace("\r\n", "\n")

return create_dojo(user, repository, public_key, private_key)


@dojo_namespace.route("/<dojo>/modules")
class GetDojoModules(Resource):
@dojo_route
def get(self, dojo):
modules = [{'id': module.id,
'module_index': module.module_index,
'name': module.name,
'description': module.description,
} for module in dojo.modules]

return {"success": True, "modules": modules}


@dojo_namespace.route("/<dojo>/<module>/challenges")
class GetDojoModuleChallenges(Resource):
@dojo_route
def get(self, dojo, module):
challenges = [{'id': challenge.id,
'challenge_id': challenge.challenge_id,
'module_index': challenge.module_index,
'challenge_index': challenge.challenge_index,
'name': challenge.name,
'description': challenge.description,
} for challenge in module.visible_challenges()]

return {"success": True, "challenges": challenges}

Loading

0 comments on commit 1ecdd2f

Please sign in to comment.