From 1dcd53a5811ecc0b97ad2e7e63adeb200452ae9a Mon Sep 17 00:00:00 2001 From: esefxdz Date: Mon, 13 Jul 2026 00:12:51 +0300 Subject: [PATCH] calendar update --- cogs/calendar.py | 145 +++++++++++++++++++++++++++++++++++++++++++++++ main.py | 1 + 2 files changed, 146 insertions(+) create mode 100644 cogs/calendar.py diff --git a/cogs/calendar.py b/cogs/calendar.py new file mode 100644 index 0000000..40e4ec4 --- /dev/null +++ b/cogs/calendar.py @@ -0,0 +1,145 @@ +"""Calendar cog — book events via Discord slash commands, displayed on the website.""" +###################################################################### +import logging +import re +from datetime import datetime + +import aiohttp +import discord +from discord import app_commands +from discord.ext import commands + +log = logging.getLogger(__name__) + +FIRESTORE_CALENDAR = ( + "https://firestore.googleapis.com/v1/projects/esef-514bf" + "/databases/(default)/documents/calendar_events" +) + +DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") +TIME_RE = re.compile(r"^\d{2}:\d{2}$") + + +class Calendar(commands.Cog): + """Book and manage calendar events from Discord.""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + + async def cog_load(self) -> None: + """Sync slash commands when the cog loads.""" + try: + await self.bot.tree.sync() + log.info("Calendar slash commands synced") + except Exception: + log.warning("Failed to sync slash commands", exc_info=True) + + # ── /book ──────────────────────────────────────────────────────────── + + @app_commands.command(name="book", description="Add an event to the calendar") + @app_commands.describe( + date="Date in YYYY-MM-DD format (e.g. 2026-07-20)", + time="Time in HH:MM 24-hour format (optional, e.g. 14:30)", + title="Event title", + description="Extra notes (optional)", + ) + async def book_cmd( + self, + interaction: discord.Interaction, + date: str, + title: str, + time: str = "", + description: str = "", + ) -> None: + """Slash command: /book [time] [description]""" + await interaction.response.defer() + + if not DATE_RE.match(date): + return await interaction.followup.send( + "❌ Invalid date. Use `YYYY-MM-DD`, e.g. `2026-07-20`." + ) + try: + datetime.strptime(date, "%Y-%m-%d") + except ValueError: + return await interaction.followup.send("❌ That date doesn't exist.") + + if time and not TIME_RE.match(time): + return await interaction.followup.send("❌ Time must be `HH:MM` (24-hour), e.g. `14:30`.") + if time: + try: + datetime.strptime(time, "%H:%M") + except ValueError: + return await interaction.followup.send("❌ Invalid time.") + + payload = { + "fields": { + "title": {"stringValue": title.strip()}, + "date": {"stringValue": date}, + "time": {"stringValue": time}, + "description": {"stringValue": description.strip()}, + "createdBy": {"stringValue": str(interaction.user)}, + } + } + + async with aiohttp.ClientSession() as session: + async with session.post(FIRESTORE_CALENDAR, json=payload) as resp: + if resp.status in (200, 201): + when = date + if time: + when += f" at {time}" + desc = f" — {description}" if description else "" + await interaction.followup.send( + f"📅 Booked **{when}**: **{title}**{desc}" + ) + else: + body = await resp.text() + log.warning("Firestore POST failed %d: %s", resp.status, body[:200]) + await interaction.followup.send("❌ Failed to save event.") + + # ── /unbook ────────────────────────────────────────────────────────── + + @app_commands.command(name="unbook", description="Remove an event from the calendar") + @app_commands.describe( + date="Date of the event (YYYY-MM-DD)", + title="Exact title of the event to remove", + ) + async def unbook_cmd( + self, + interaction: discord.Interaction, + date: str, + title: str, + ) -> None: + """Slash command: /unbook <date> <title>""" + await interaction.response.defer() + + if not DATE_RE.match(date) or not title.strip(): + return await interaction.followup.send( + "❌ Usage: `/unbook YYYY-MM-DD Title`" + ) + + deleted = False + async with aiohttp.ClientSession() as session: + async with session.get(FIRESTORE_CALENDAR) as resp: + if resp.status != 200: + return await interaction.followup.send("❌ Could not fetch events.") + data = await resp.json() + + for doc in data.get("documents", []): + fields = doc.get("fields", {}) + d = fields.get("date", {}).get("stringValue", "") + t = fields.get("title", {}).get("stringValue", "") + if d == date and t.lower() == title.strip().lower(): + doc_name = doc["name"].split("/")[-1] + async with session.delete(f"{FIRESTORE_CALENDAR}/{doc_name}") as dresp: + if dresp.status == 200: + deleted = True + break + + if deleted: + await interaction.followup.send(f"🗑️ Removed **{title}** from {date}.") + else: + await interaction.followup.send(f"❌ No event **{title}** found on {date}.") + + +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Calendar(bot)) diff --git a/main.py b/main.py index 8020a13..6fa53f7 100644 --- a/main.py +++ b/main.py @@ -71,6 +71,7 @@ async def main(): await bot.load_extension('blue_archive.gacha') await bot.load_extension('blue_archive.inventory') await bot.load_extension('firebase_website') + await bot.load_extension('cogs.calendar') try: await bot.start(os.getenv('DISCORD_TOKEN'))