|
1 | 1 | #!/usr/bin/env python3 |
2 | 2 |
|
3 | | -import discord |
4 | | -from discord import embeds |
5 | | -from discord.ext import tasks, commands |
6 | 3 | import logging |
7 | | -import os |
| 4 | +import asyncio |
| 5 | +import glob |
| 6 | +import pathlib |
| 7 | + |
| 8 | +import discord |
| 9 | +from discord.ext import commands |
8 | 10 |
|
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 |
13 | 13 |
|
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 | +) |
15 | 20 | logging.getLogger("discord.client").setLevel(logging.ERROR) |
16 | 21 | logger = logging.getLogger(__name__) |
17 | 22 |
|
| 23 | +# bot |
18 | 24 | intents = discord.Intents.default() |
19 | 25 | intents.members = True |
20 | 26 | intents.guilds = True |
|
23 | 29 |
|
24 | 30 | bot = commands.Bot(intents=intents) |
25 | 31 |
|
26 | | -known_events = load_known_events() |
27 | | - |
28 | | -emoji = "🚩" |
29 | | - |
30 | 32 | @bot.event |
31 | 33 | async def on_ready(): |
32 | 34 | 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}") |
90 | 35 |
|
91 | | - if new_events_found: |
92 | | - save_known_events(known_events) |
93 | 36 |
|
| 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) |
94 | 48 |
|
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 |
153 | 49 |
|
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)}") |
158 | 60 |
|
159 | | - channel = bot.ctf_channel |
160 | | - await channel.set_permissions(member, view_channel=True) |
161 | | - print(f"{member} 已加入 {channel.name}") |
162 | 61 |
|
163 | 62 | if __name__ == "__main__": |
164 | | - main() |
| 63 | + load_cogs() |
| 64 | + asyncio.run(main()) |
0 commit comments