Skip to content

Commit 970e19d

Browse files
authored
Merge pull request #16 from pictures2333/master
feat: rewrite ctf events detection, notification and ctf channel creation
2 parents 4a0cae0 + d647a23 commit 970e19d

18 files changed

+1083
-197
lines changed

.env.example

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ DISCORD_BOT_TOKEN=your_discord_bot_token_here
33

44
# CTF Tracking Configuration
55
CHECK_INTERVAL_MINUTES=30
6-
ANNOUNCEMENT_CHANNEL_ID=your_channel_id_here
6+
ANNOUNCEMENT_CHANNEL_NAME=your_channel_name_here
77

8-
# CTFtime API Configuration
9-
CTFTIME_API_BASE_URL=https://ctftime.org/api/v1
8+
# Misc
9+
TIMEZONE="Asia/Taipei"

ctfeed.py

Lines changed: 38 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
#!/usr/bin/env python3
22

3-
import discord
4-
from discord import embeds
5-
from discord.ext import tasks, commands
63
import logging
7-
import os
4+
import asyncio
5+
import glob
6+
import pathlib
7+
8+
import discord
9+
from discord.ext import commands
810

9-
from src.config import DISCORD_BOT_TOKEN, ANNOUNCEMENT_CHANNEL_ID, CHECK_INTERVAL
10-
from src.data_manager import load_known_events, save_known_events
11-
from src.ctf_api import fetch_ctf_events
12-
from src.embed_creator import create_event_embed
11+
from src.config import settings
12+
from src.database.database import init_db
1313

14-
logging.basicConfig(level=logging.INFO)
14+
# logging
15+
logging.basicConfig(
16+
level=logging.INFO,
17+
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
18+
datefmt="%Y-%m-%d %H:%M:%S",
19+
)
1520
logging.getLogger("discord.client").setLevel(logging.ERROR)
1621
logger = logging.getLogger(__name__)
1722

23+
# bot
1824
intents = discord.Intents.default()
1925
intents.members = True
2026
intents.guilds = True
@@ -23,142 +29,36 @@
2329

2430
bot = commands.Bot(intents=intents)
2531

26-
known_events = load_known_events()
27-
28-
emoji = "🚩"
29-
3032
@bot.event
3133
async def on_ready():
3234
logger.info(f"Bot logged in: {bot.user}")
33-
if known_events:
34-
logger.info(
35-
f"Loading data/known_events.json for {len(known_events)} known competitions"
36-
)
37-
else:
38-
logger.info(
39-
"No known competitions found, creating new file data/known_events.json"
40-
)
41-
save_known_events(set())
42-
check_new_events.start()
43-
44-
45-
@tasks.loop(minutes=CHECK_INTERVAL)
46-
async def check_new_events():
47-
global known_events
48-
49-
events = await fetch_ctf_events()
50-
51-
channel_name = ANNOUNCEMENT_CHANNEL_ID
52-
if not channel_name:
53-
logger.error("Please set ANNOUNCEMENT_CHANNEL_ID in .env file")
54-
logger.error("For example: ANNOUNCEMENT_CHANNEL_ID=ctftime")
55-
logger.error("Please check if the .env file is correctly set")
56-
await bot.close()
57-
return
58-
59-
channel = None
60-
for guild in bot.guilds:
61-
for text_channel in guild.text_channels:
62-
if text_channel.name.lower() == channel_name.lower():
63-
channel = text_channel
64-
break
65-
if channel:
66-
break
67-
68-
if not channel:
69-
logger.error(f"Can't find channel named '{channel_name}'")
70-
logger.error(f"Please check:")
71-
logger.error(f"1. Channel name is correct: {channel_name}")
72-
logger.error(f"2. Bot has permission to view the channel")
73-
logger.error(f"3. The channel exists in the server where the Bot is located")
74-
await bot.close()
75-
return
76-
77-
new_events_found = False
78-
for event in events:
79-
event_id = event["id"]
80-
if event_id not in known_events:
81-
known_events.add(event_id)
82-
new_events_found = True
83-
84-
embed = await create_event_embed(event)
85-
try:
86-
await channel.send(embed=embed)
87-
logger.info(f"Sent new event notification: {event['title']}")
88-
except Exception as e:
89-
logger.error(f"Failed to send notification: {e}")
9035

91-
if new_events_found:
92-
save_known_events(known_events)
9336

37+
async def main():
38+
# setup
39+
40+
# initializing database
41+
logger.info("Initializing database...")
42+
await init_db()
43+
44+
# start
45+
logger.info(f"Starting CTF Bot...")
46+
async with bot:
47+
await bot.start(settings.DISCORD_BOT_TOKEN)
9448

95-
@check_new_events.before_loop
96-
async def before_check():
97-
await bot.wait_until_ready()
98-
99-
100-
def main():
101-
if not DISCORD_BOT_TOKEN:
102-
print("Please set DISCORD_BOT_TOKEN in .env file")
103-
exit(1)
104-
105-
print("Start CTF Bot...")
106-
bot.run(DISCORD_BOT_TOKEN)
107-
108-
109-
@bot.slash_command(name = "create_ctf_channel", description = "Create a CTF channel in the CTF category")
110-
async def create_CTF_channel(ctx, channel_name:str):
111-
category_name = "Incoming/Running CTF"
112-
guild = ctx.guild
113-
category = discord.utils.get(ctx.guild.categories, name=category_name)
114-
115-
if category is None:
116-
await ctx.send(f"Category '{category_name}' not found.")
117-
return
118-
119-
overwrites = {
120-
guild.default_role: discord.PermissionOverwrite(view_channel=False),
121-
ctx.author: discord.PermissionOverwrite(view_channel=True),
122-
guild.me: discord.PermissionOverwrite(view_channel=True)
123-
}
124-
125-
try:
126-
new_channel = await guild.create_text_channel(channel_name, category=category, overwrites=overwrites)
127-
msg = await new_channel.send(
128-
embed = discord.Embed(
129-
title=f"{ctx.author.display_name} 發起了 {channel_name}!",
130-
)
131-
)
132-
133-
public_msg = await ctx.send(
134-
embed = discord.Embed(
135-
title=f"新CTF頻道創建{channel_name}",
136-
description=f"加入請按下面的Emoji{emoji}"
137-
)
138-
)
139-
140-
await public_msg.add_reaction(emoji)
141-
bot.ctf_join_message_id = public_msg.id
142-
bot.ctf_channel = new_channel
143-
except Exception as e:
144-
await ctx.send(f"Failed to create channel: {e}")
145-
146-
@bot.event
147-
async def on_raw_reaction_add(payload):
148-
if payload.message_id != getattr(bot, "ctf_join_message_id", None):
149-
return
150-
151-
if str(payload.emoji) != emoji:
152-
return
15349

154-
guild = bot.get_guild(payload.guild_id)
155-
member = guild.get_member(payload.user_id)
156-
if member.bot:
157-
return
50+
# cogs
51+
def load_cogs():
52+
for filename in glob.glob("./src/cogs/*.py"):
53+
name = pathlib.Path(filename).name.split(".")[0]
54+
extension_name = f"src.cogs.{name}"
55+
try:
56+
bot.load_extension(extension_name)
57+
logger.info(f"{extension_name} loaded")
58+
except Exception as e:
59+
logger.error(f"failed to load {extension_name}: {str(e)}")
15860

159-
channel = bot.ctf_channel
160-
await channel.set_permissions(member, view_channel=True)
161-
print(f"{member} 已加入 {channel.name}")
16261

16362
if __name__ == "__main__":
164-
main()
63+
load_cogs()
64+
asyncio.run(main())

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ dependencies = [
1111
"asyncio-throttle>=1.0.2",
1212
"pytz>=2023.3",
1313
"audioop-lts>=0.2.2",
14+
"sqlalchemy>=2.0.45",
15+
"aiosqlite>=0.22.1",
16+
"pydantic-settings>=2.12.0",
1417
]
1518

1619
[dependency-groups]

setup_env.sh

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,29 @@ echo "Announcement Channel ID:"
114114
echo " The Discord channel where CTF announcements will be posted"
115115
echo " The channel name that on your server"
116116
while true; do
117-
read -p " Paste your Discord Channel Name: " ANNOUNCEMENT_CHANNEL_ID
118-
if [ -n "$ANNOUNCEMENT_CHANNEL_ID" ]; then
117+
read -p " Paste your Discord Channel Name: " ANNOUNCEMENT_CHANNEL_NAME
118+
if [ -n "$ANNOUNCEMENT_CHANNEL_NAME" ]; then
119119
break
120120
else
121121
echo " Error: Discord Channel Name cannot be empty. Please try again."
122122
fi
123123
done
124124

125+
echo ""
126+
echo "Time zone:"
127+
echo " The display time zone"
128+
echo " Default is Asia/Taipei"
129+
read -p " Enter time zone (press Enter for default Asia/Taipei): " TIMEZONE
130+
if [ -z "$TIMEZONE" ]; then
131+
TIMEZONE="Asia/Taipei"
132+
fi
133+
125134
echo ""
126135
echo "Please review your configuration:"
127136
echo " Discord Bot Token: ${DISCORD_BOT_TOKEN:0:30}********** (hidden)"
128137
echo " Check Interval: $CHECK_INTERVAL_MINUTES minutes"
129-
echo " Announcement Channel Name: $ANNOUNCEMENT_CHANNEL_ID"
138+
echo " Announcement Channel Name: $ANNOUNCEMENT_CHANNEL_NAME"
139+
echo " Time Zone: $TIMEZONE"
130140
echo ""
131141

132142
while true; do
@@ -156,10 +166,10 @@ DISCORD_BOT_TOKEN=$DISCORD_BOT_TOKEN
156166
157167
# CTF Tracking Configuration
158168
CHECK_INTERVAL_MINUTES=$CHECK_INTERVAL_MINUTES
159-
ANNOUNCEMENT_CHANNEL_ID=$ANNOUNCEMENT_CHANNEL_ID
169+
ANNOUNCEMENT_CHANNEL_NAME=$ANNOUNCEMENT_CHANNEL_NAME
160170
161-
# CTFtime API Configuration
162-
CTFTIME_API_BASE_URL=https://ctftime.org/api/v1
171+
# Misc
172+
TIMEZONE="$TIMEZONE"
163173
EOF
164174

165175
echo -e "${GREEN}✓ Configuration complete! .env file created successfully.${NC}"

0 commit comments

Comments
 (0)