import json import re # needs the following version of discord.py for recurring events to work: https://github.com/DA-344/d.py/tree/recurrent_events # uncomment following lines # import sys # sys.path.insert(1, '/path/to/Posthorn/vendor/d.py') import discord from discord.ext import commands, tasks from dotenv import load_dotenv from os import getenv from github_connector import create_or_update_file, delete_file, similar_exists load_dotenv("./.env") DISCORD_TOKEN = getenv("DISCORD_TOKEN") # Intents are required to listen to events like messages intents = discord.Intents.default() intents.message_content = True intents.members = True intents.reactions = True intents.dm_messages = True # Create an instance of the Bot bot = commands.Bot(command_prefix="!", intents=intents) def getCommitMessage(event): return f"{event.start_time.strftime('%Y-%m-%d')}_{event.name}" def getFilePath(event): return f"_events/{event.start_time.strftime('%Y')}/discord-event-{event.id}.md" def getDay(day): match day: case 0: return 'MO' case 1: return 'TU' case 2: return 'WE' case 3: return 'TH' case 4: return 'FR' case 5: return 'SA' case 6: return 'SU' case _: raise Exception(f'no matching day for int {day}') def getRrule(event): byDay = False if hasattr(event.recurrence_rule, "n_weekdays"): weekdays = event.recurrence_rule.n_weekdays i = 0 for week, day in weekdays: comma = '' if i < len(weekdays) - 1: comma = ',' byDay = f'{str(week)}{getDay(day)}{comma}' i += 1 if byDay: return f"FREQ={event.recurrence_rule.frequency.name.upper()};INTERVAL={event.recurrence_rule.interval};BYDAY={byDay}" else: return f"FREQ={event.recurrence_rule.frequency.name.upper()};INTERVAL={event.recurrence_rule.interval}" def getEventYML(event): location = "Leibnizstraรe 32, 39104 Magdeburg" if hasattr(event, 'location'): location = event.location if hasattr(event, "recurrence_rule") and hasattr(event.recurrence_rule, "frequency") and hasattr(event.recurrence_rule, "interval"): return f'---\nlayout: event\ntitle: "{event.name}"\nauthor: "Netz39 e.V." \nevent:\n start: {event.start_time.strftime("%Y-%m-%d %H:%M:%S")} \n end: {event.end_time.strftime("%Y-%m-%d %H:%M:%S")} \n organizer: "Netz39 Team <kontakt@netz39.de>" \n location: "{location}"\n rrule: "{getRrule(event)}"\n---\n<!-- event imported from discord manual changes may be overwritten -->\n{event.description}' else: return f'---\nlayout: event\ntitle: "{event.name}"\nauthor: "Netz39 e.V." \nevent:\n start: {event.start_time.strftime("%Y-%m-%d %H:%M:%S")} \n end: {event.end_time.strftime("%Y-%m-%d %H:%M:%S")} \n organizer: "Netz39 Team <kontakt@netz39.de>" \n location: "{location}"\n---\n<!-- event imported from discord manual changes may be overwritten -->\n{event.description}' def getAdmins(guild): admins = [] for member in guild.members: if member.guild_permissions.administrator: admins.append(member.id) return admins @tasks.loop(hours=24) # Runs every 7 days async def check_events(): await bot.wait_until_ready() for guild in bot.guilds: scheduledEvents = await guild.fetch_scheduled_events() for event in scheduledEvents: await handleEvent(event, True) async def handleEvent(event, isUpdate = False): guild = event.guild channel = guild.system_channel admins = getAdmins(guild) eventYML = getEventYML(event) file_path = getFilePath(event) commit_message = getCommitMessage(event) if event.creator_id in admins or similar_exists(event.name): create_or_update_file( file_path, eventYML, f"new event: {commit_message}", ) if not isUpdate: embed = discord.Embed( title=f"๐ New event on {event.start_time.strftime('%Y-%m-%d')}", description=f"### {event.name}\n\n{event.description}\n\n", color=discord.Color.light_embed() ) if hasattr(event, "cover_image") and event.cover_image: embed.set_image(url=event.cover_image.url) embed.add_field(name="๐ Time", value=event.start_time.strftime("%Y-%m-%d %HUhr"), inline=True) if hasattr(event, 'location'): embed.add_field(name="๐ Location", value=event.location, inline=True) eventLink = f"[๐ Netz39](https://www.netz39.de/events/2025/discord-event-{event.id})"; embed.add_field(value=eventLink, name="", inline=False) embed.set_footer(text="Donโt miss it! ") await channel.send(embed=embed) else: eventJSON = json.dumps( { "yml": eventYML, "path": file_path, "fileName": commit_message, "name": event.name, "date": event.start_time.strftime("%Y-%m-%d"), }, indent=2, ) for admin in admins: user = await bot.fetch_user(event.creator_id) await sendDm( admin, f"{user.name} added a new Event on {event.start_time.strftime('%Y')}: {event.name}. Like this message to approve.\n\n\n```json\n{eventJSON}```", ) # Event: When the bot is ready @bot.event async def on_ready(): print(f"Logged in as {discord.user}") # Command: !hello @bot.command() async def hello(ctx): await ctx.send( "Hello, I am a bot to add events from discord to the Netz39 calendar on the website!" ) @bot.event async def on_ready(): print(f"Logged in as {bot.user}") for guild in bot.guilds: system_channel = guild.system_channel if system_channel: await system_channel.send("Hello everyone! I'm online and ready to go! ๐ ") await check_events() @bot.event async def on_scheduled_event_update(before, after): if after.status != "canceled": await handleEvent(after, True) @bot.event async def on_scheduled_event_create(event): await handleEvent(event) async def sendDm(userID, message): user = await bot.fetch_user(userID) await user.send(message) @bot.event async def on_reaction_add(reaction, user): message = reaction.message channel = message.channel guild = message.guild admins = getAdmins(guild) if not user.id in admins: return # Ignore non-maintainers if user.bot: return # Ignore bot reactions if ( isinstance(message.channel, discord.DMChannel) and message.author.bot ): # Check if it's a DM and from the bot if reaction.emoji == "๐": messageJSON = json.loads( re.search( "(?s)(?<=```json).*?(?=```)", reaction.message.content ).group() ) create_or_update_file( messageJSON["path"], messageJSON["yml"], f"new event: {messageJSON['fileName']}", ) await channel.send( f"๐ New event '{messageJSON['name']}' on {messageJSON['date']}." ) @bot.event async def on_scheduled_event_delete(event): delete_file(getFilePath(event)) guild = event.guild channel = guild.system_channel await channel.send(f"โ Event has been canceled: {event.name}") bot.run(DISCORD_TOKEN)