bucketlist feature added
This commit is contained in:
1 parent
14926aa77b
commit
bf4ea13d4b
3 files changed
+212
-1
No files matched your search
@@ -0,0 +1,207 @@
|
|||||||
|
"""Bucketlist cog — modal-based bucket list, Firestore Admin SDK."""
|
||||||
|
######################################################################
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from firebase_website import get_db
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MONTHS = [
|
||||||
|
"January", "February", "March", "April", "May", "June",
|
||||||
|
"July", "August", "September", "October", "November", "December",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# BUCKET MODAL
|
||||||
|
# ==========================================
|
||||||
|
|
||||||
|
class BucketModal(discord.ui.Modal, title="Add to Bucketlist"):
|
||||||
|
"""Popup form for adding a bucketlist item."""
|
||||||
|
|
||||||
|
item_title = discord.ui.TextInput(
|
||||||
|
label="Title",
|
||||||
|
placeholder="Visit Japan",
|
||||||
|
max_length=80,
|
||||||
|
)
|
||||||
|
description = discord.ui.TextInput(
|
||||||
|
label="Description (optional)",
|
||||||
|
placeholder="Explore Tokyo, eat real ramen...",
|
||||||
|
max_length=200,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def on_submit(self, interaction: discord.Interaction) -> None:
|
||||||
|
title = self.item_title.value.strip()
|
||||||
|
description = self.description.value.strip()
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
utc_ts = now.isoformat()
|
||||||
|
month_name = MONTHS[now.month - 1]
|
||||||
|
date_display = f"Added {month_name} {now.year}"
|
||||||
|
|
||||||
|
doc = {
|
||||||
|
"title": title,
|
||||||
|
"description": description,
|
||||||
|
"date": date_display,
|
||||||
|
"utc": utc_ts,
|
||||||
|
"done": False,
|
||||||
|
"createdBy": str(interaction.user),
|
||||||
|
}
|
||||||
|
|
||||||
|
cog = interaction.client.get_cog("Bucketlist")
|
||||||
|
if cog is None:
|
||||||
|
return await interaction.response.send_message(
|
||||||
|
"Cog not ready. Try again.", ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
await cog._items_ref(db).add(doc)
|
||||||
|
except Exception:
|
||||||
|
log.exception("Failed to add bucketlist item")
|
||||||
|
return await interaction.response.send_message(
|
||||||
|
"Failed to save. Try again.", ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
desc = f" — {description}" if description else ""
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"Added **{title}**{desc}\nhttps://esefos.netlify.app/bucketlist",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# UNBUCKET DROPDOWN
|
||||||
|
# ==========================================
|
||||||
|
|
||||||
|
class UnbucketSelect(discord.ui.Select):
|
||||||
|
"""Dropdown of bucketlist items — pick one to delete."""
|
||||||
|
|
||||||
|
def __init__(self, items: list[dict], cog) -> None:
|
||||||
|
self._items = {it["doc_id"]: it for it in items}
|
||||||
|
self._cog = cog
|
||||||
|
super().__init__(
|
||||||
|
placeholder="Pick an item to remove…",
|
||||||
|
options=[
|
||||||
|
discord.SelectOption(
|
||||||
|
label=f"{it['title']}"[:100],
|
||||||
|
description=f"{it['date']} — {it['createdBy']}"[:100],
|
||||||
|
value=it["doc_id"],
|
||||||
|
)
|
||||||
|
for it in items[:25]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction) -> None:
|
||||||
|
doc_id = self.values[0]
|
||||||
|
it = self._items[doc_id]
|
||||||
|
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
await self._cog._items_ref(db).document(doc_id).delete()
|
||||||
|
except Exception:
|
||||||
|
log.exception("Failed to delete bucketlist item")
|
||||||
|
return await interaction.response.send_message(
|
||||||
|
"Failed to remove. Try again.", ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"Removed **{it['title']}** from the bucketlist.", ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UnbucketView(discord.ui.View):
|
||||||
|
def __init__(self, items: list[dict], cog) -> None:
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
self.add_item(UnbucketSelect(items, cog))
|
||||||
|
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# BUCKET BUTTON
|
||||||
|
# ==========================================
|
||||||
|
|
||||||
|
class BucketButton(discord.ui.View):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(timeout=300)
|
||||||
|
|
||||||
|
@discord.ui.button(label="Add Item", style=discord.ButtonStyle.primary, emoji="📝")
|
||||||
|
async def open_modal(self, interaction: discord.Interaction, _button):
|
||||||
|
await interaction.response.send_modal(BucketModal())
|
||||||
|
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# BUCKETLIST COG
|
||||||
|
# ==========================================
|
||||||
|
|
||||||
|
class Bucketlist(commands.Cog):
|
||||||
|
"""Add/remove bucketlist items via a popup form — restricted to authorized users."""
|
||||||
|
|
||||||
|
def __init__(self, bot: commands.Bot) -> None:
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
# build the subcollection path key from credentials
|
||||||
|
bl_user = os.getenv("BUCKETLIST_USER", "")
|
||||||
|
bl_pass = os.getenv("BUCKETLIST_PASS", "")
|
||||||
|
combined = f"{bl_user}:{bl_pass}"
|
||||||
|
self._hash = hashlib.sha256(combined.encode()).hexdigest()
|
||||||
|
|
||||||
|
def _items_ref(self, db):
|
||||||
|
"""Return the Firestore subcollection ref gated behind the credential hash."""
|
||||||
|
return db.collection("bucketlist_data").document(self._hash).collection("items")
|
||||||
|
|
||||||
|
#this command lists all the bucketlist commands, not an actual bucketlist command##
|
||||||
|
@commands.command()
|
||||||
|
async def bucketlist(self, ctx):
|
||||||
|
bucketlist_commands = [f'!{cmd.name}' for cmd in self.get_commands()]
|
||||||
|
await ctx.reply('\n'.join(bucketlist_commands))
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# commands
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
@commands.command(name="bucket")
|
||||||
|
async def bucket_cmd(self, ctx: commands.Context) -> None:
|
||||||
|
"""Send a button that opens the add-item form."""
|
||||||
|
await ctx.reply("Click below to add an item:", view=BucketButton())
|
||||||
|
|
||||||
|
@commands.command(name="unbucket")
|
||||||
|
async def unbucket_cmd(self, ctx: commands.Context) -> None:
|
||||||
|
"""Open a dropdown to pick and remove a bucketlist item."""
|
||||||
|
await self._unbucket_dropdown(ctx)
|
||||||
|
|
||||||
|
async def _unbucket_dropdown(self, ctx: commands.Context) -> None:
|
||||||
|
"""Fetch all bucketlist items and present a selection dropdown."""
|
||||||
|
try:
|
||||||
|
db = get_db()
|
||||||
|
all_docs = await self._items_ref(db).order_by("utc").get()
|
||||||
|
except Exception:
|
||||||
|
log.exception("Failed to fetch bucketlist items")
|
||||||
|
return await ctx.reply("Could not fetch items.")
|
||||||
|
|
||||||
|
items: list[dict] = []
|
||||||
|
|
||||||
|
for doc in all_docs:
|
||||||
|
data = doc.to_dict()
|
||||||
|
items.append({
|
||||||
|
"doc_id": doc.id,
|
||||||
|
"title": data.get("title", ""),
|
||||||
|
"date": data.get("date", ""),
|
||||||
|
"createdBy": data.get("createdBy", ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
if not items:
|
||||||
|
return await ctx.reply("📭 Nothing in the bucketlist yet.")
|
||||||
|
|
||||||
|
await ctx.reply("Pick an item to remove:", view=UnbucketView(items, self))
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot: commands.Bot) -> None:
|
||||||
|
await bot.add_cog(Bucketlist(bot))
|
||||||
@@ -40,4 +40,7 @@ timestamp friends — format: TIMESTAMP_FRIEND_{DISCORD_USER_ID}=Country
|
|||||||
# Examples:
|
# Examples:
|
||||||
TIMESTAMP_FRIEND_123456789012345678=Turkey
|
TIMESTAMP_FRIEND_123456789012345678=Turkey
|
||||||
TIMESTAMP_FRIEND_987654321098765432=Japan
|
TIMESTAMP_FRIEND_987654321098765432=Japan
|
||||||
TIMESTAMP_FRIEND_555555555555555555=US East
|
TIMESTAMP_FRIEND_555555555555555555=US East
|
||||||
|
|
||||||
|
#bucketlist access
|
||||||
|
BUCKETLIST_ACCESS=123456789012345678
|
||||||
@@ -74,6 +74,7 @@ async def main():
|
|||||||
await bot.load_extension('blue_archive.inventory')
|
await bot.load_extension('blue_archive.inventory')
|
||||||
await bot.load_extension('firebase_website')
|
await bot.load_extension('firebase_website')
|
||||||
await bot.load_extension('cogs.calendar')
|
await bot.load_extension('cogs.calendar')
|
||||||
|
await bot.load_extension('cogs.bucketlist')
|
||||||
await bot.load_extension('cogs.timestamp')
|
await bot.load_extension('cogs.timestamp')
|
||||||
await bot.load_extension('cogs.timestamp_friends')
|
await bot.load_extension('cogs.timestamp_friends')
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user