Implemented /stage edit and /stage delete (#2)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from logging import Logger
|
||||
from typing import Any
|
||||
from typing import Any, override
|
||||
|
||||
from bson import ObjectId
|
||||
from discord import Guild, User
|
||||
@@ -43,6 +43,14 @@ class PycordBot(LibPycordBot):
|
||||
if "cache" in self.config and self.config["cache"]["type"] is not None:
|
||||
self.cache = create_cache_client(self.config, self.config["cache"]["type"])
|
||||
|
||||
@override
|
||||
async def start(self, *args: Any, **kwargs: Any) -> None:
|
||||
await super().start(*args, **kwargs)
|
||||
|
||||
@override
|
||||
async def close(self, **kwargs) -> None:
|
||||
await super().close(**kwargs)
|
||||
|
||||
async def find_user(self, user: int | User) -> PycordUser:
|
||||
"""Find User by its ID or User object.
|
||||
|
||||
@@ -100,16 +108,11 @@ class PycordBot(LibPycordBot):
|
||||
|
||||
event_stage: PycordEventStage = await PycordEventStage.create(**kwargs, cache=self.cache)
|
||||
|
||||
await event.insert_stage(event_stage._id, kwargs["sequence"], cache=self.cache)
|
||||
await event.insert_stage(self, event_stage._id, kwargs["sequence"], cache=self.cache)
|
||||
|
||||
return event_stage
|
||||
|
||||
async def start(self, *args: Any, **kwargs: Any) -> None:
|
||||
await super().start(*args, **kwargs)
|
||||
|
||||
async def close(self, **kwargs) -> None:
|
||||
await super().close(**kwargs)
|
||||
|
||||
# TODO Document this method
|
||||
async def find_event(self, event_id: str | ObjectId | None = None, event_name: str | None = None):
|
||||
if event_id is None and event_name is None:
|
||||
raise AttributeError("Either event's ID or name must be provided!")
|
||||
@@ -118,3 +121,7 @@ class PycordBot(LibPycordBot):
|
||||
return await PycordEvent.from_id(event_id, cache=self.cache)
|
||||
else:
|
||||
return await PycordEvent.from_name(event_name, cache=self.cache)
|
||||
|
||||
# TODO Document this method
|
||||
async def find_event_stage(self, stage_id: str | ObjectId) -> PycordEventStage:
|
||||
return await PycordEventStage.from_id(stage_id, cache=self.cache)
|
||||
|
@@ -5,10 +5,11 @@ from typing import Any, Dict, List, Optional
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from bson import ObjectId
|
||||
from discord import Bot
|
||||
from libbot.cache.classes import Cache
|
||||
from pymongo.results import InsertOneResult
|
||||
|
||||
from modules.database import col_events, col_stages
|
||||
from modules.database import col_events
|
||||
from modules.logging_utils import get_logger
|
||||
|
||||
logger: Logger = get_logger(__name__)
|
||||
@@ -266,14 +267,62 @@ class PycordEvent:
|
||||
await self.__collection__.delete_one({"_id": self._id})
|
||||
self._delete_cache(cache)
|
||||
|
||||
# TODO Add documentation
|
||||
async def cancel(self, cache: Optional[Cache] = None):
|
||||
await self._set(cache, cancelled=True)
|
||||
|
||||
async def insert_stage(
|
||||
self, event_stage_id: ObjectId, sequence: int, cache: Optional[Cache] = None
|
||||
async def _update_event_stage_order(
|
||||
self,
|
||||
bot: Any,
|
||||
old_stage_ids: List[ObjectId],
|
||||
cache: Optional[Cache] = None,
|
||||
) -> None:
|
||||
self.stage_ids.insert(sequence, event_stage_id)
|
||||
await self._set(cache, stage_ids=self.stage_ids)
|
||||
logger.info("Updating event stages order for %s...", self._id)
|
||||
|
||||
# TODO Check if this works
|
||||
await col_stages.update_many({"_id": {"$eq": self.stage_ids[sequence:]}}, {"$inc": {"sequence": 1}})
|
||||
logger.debug("Old stage IDs: %s", old_stage_ids)
|
||||
logger.debug("New stage IDs: %s", self.stage_ids)
|
||||
|
||||
for event_stage_id in self.stage_ids:
|
||||
if event_stage_id not in old_stage_ids:
|
||||
continue
|
||||
|
||||
stage_index: int = self.stage_ids.index(event_stage_id)
|
||||
old_stage_index: int = old_stage_ids.index(event_stage_id)
|
||||
|
||||
logger.debug(
|
||||
"Indexes for %s: was %s and is now %s", event_stage_id, old_stage_index, stage_index
|
||||
)
|
||||
|
||||
if stage_index != old_stage_index:
|
||||
await (await bot.find_event_stage(event_stage_id)).update(cache, sequence=stage_index)
|
||||
|
||||
# TODO Add documentation
|
||||
async def insert_stage(
|
||||
self, bot: Bot, event_stage_id: ObjectId, index: int, cache: Optional[Cache] = None
|
||||
) -> None:
|
||||
old_stage_ids: List[ObjectId] = self.stage_ids.copy()
|
||||
|
||||
self.stage_ids.insert(index, event_stage_id)
|
||||
|
||||
await self._set(cache, stage_ids=self.stage_ids)
|
||||
await self._update_event_stage_order(bot, old_stage_ids, cache=cache)
|
||||
|
||||
# TODO Add documentation
|
||||
async def reorder_stage(
|
||||
self, bot: Any, event_stage_id: ObjectId, index: int, cache: Optional[Cache] = None
|
||||
) -> None:
|
||||
old_stage_ids: List[ObjectId] = self.stage_ids.copy()
|
||||
|
||||
self.stage_ids.insert(index, self.stage_ids.pop(self.stage_ids.index(event_stage_id)))
|
||||
|
||||
await self._set(cache, stage_ids=self.stage_ids)
|
||||
await self._update_event_stage_order(bot, old_stage_ids, cache=cache)
|
||||
|
||||
# TODO Add documentation
|
||||
async def remove_stage(self, bot: Bot, event_stage_id: ObjectId, cache: Optional[Cache] = None) -> None:
|
||||
old_stage_ids: List[ObjectId] = self.stage_ids.copy()
|
||||
|
||||
self.stage_ids.pop(self.stage_ids.index(event_stage_id))
|
||||
|
||||
await self._set(cache, stage_ids=self.stage_ids)
|
||||
await self._update_event_stage_order(bot, old_stage_ids, cache=cache)
|
||||
|
@@ -38,7 +38,7 @@ class PycordEventStage:
|
||||
creator_id: int
|
||||
question: str
|
||||
answer: str
|
||||
media: List[str]
|
||||
media: List[int]
|
||||
|
||||
@classmethod
|
||||
async def from_id(cls, stage_id: str | ObjectId, cache: Optional[Cache] = None) -> "PycordEventStage":
|
||||
|
@@ -12,7 +12,7 @@ from discord.utils import basic_autocomplete
|
||||
|
||||
from classes import PycordGuild
|
||||
from classes.pycord_bot import PycordBot
|
||||
from modules.utils import autofill_timezones, autofill_languages
|
||||
from modules.utils import autocomplete_timezones, autocomplete_languages
|
||||
|
||||
|
||||
class Config(Cog):
|
||||
@@ -34,13 +34,13 @@ class Config(Cog):
|
||||
@option(
|
||||
"timezone",
|
||||
description="Timezone in which events take place",
|
||||
autocomplete=basic_autocomplete(autofill_timezones),
|
||||
autocomplete=basic_autocomplete(autocomplete_timezones),
|
||||
required=True,
|
||||
)
|
||||
@option(
|
||||
"language",
|
||||
description="Language for bot's messages",
|
||||
autocomplete=basic_autocomplete(autofill_languages),
|
||||
autocomplete=basic_autocomplete(autocomplete_languages),
|
||||
required=True,
|
||||
)
|
||||
async def command_config_set(
|
||||
@@ -97,6 +97,11 @@ class Config(Cog):
|
||||
async def command_config_show(self, ctx: ApplicationContext) -> None:
|
||||
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
|
||||
|
||||
if not guild.is_configured():
|
||||
# TODO Make a nice message
|
||||
await ctx.respond("Guild is not configured.")
|
||||
return
|
||||
|
||||
# TODO Make a nice message
|
||||
await ctx.respond(
|
||||
f"**Guild config**\n\nChannel: <#{guild.channel_id}>\nCategory: <#{guild.category_id}>\nTimezone: {guild.timezone}\nLanguage: {guild.language}"
|
||||
|
@@ -15,7 +15,7 @@ from discord.utils import basic_autocomplete
|
||||
from classes import PycordEvent, PycordGuild
|
||||
from classes.pycord_bot import PycordBot
|
||||
from modules.database import col_events
|
||||
from modules.utils import autofill_active_events
|
||||
from modules.utils import autocomplete_active_events
|
||||
|
||||
|
||||
# TODO Move to staticmethod or to a separate module
|
||||
@@ -120,7 +120,7 @@ class Event(Cog):
|
||||
@option(
|
||||
"event",
|
||||
description="Name of the event",
|
||||
autocomplete=basic_autocomplete(autofill_active_events),
|
||||
autocomplete=basic_autocomplete(autocomplete_active_events),
|
||||
required=True,
|
||||
)
|
||||
@option("name", description="New name of the event", required=False)
|
||||
@@ -192,14 +192,21 @@ class Event(Cog):
|
||||
@option(
|
||||
"event",
|
||||
description="Name of the event",
|
||||
autocomplete=basic_autocomplete(autofill_active_events),
|
||||
autocomplete=basic_autocomplete(autocomplete_active_events),
|
||||
required=True,
|
||||
)
|
||||
@option("confirm", description="Confirmation of the operation", required=False)
|
||||
async def command_event_cancel(
|
||||
self,
|
||||
ctx: ApplicationContext,
|
||||
event: str,
|
||||
confirm: bool = False,
|
||||
) -> None:
|
||||
if confirm is None or not confirm:
|
||||
# TODO Make a nice message
|
||||
await ctx.respond("Operation not confirmed.")
|
||||
return
|
||||
|
||||
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
|
||||
pycord_event: PycordEvent = await self.bot.find_event(event_id=event)
|
||||
|
||||
@@ -239,7 +246,7 @@ class Event(Cog):
|
||||
@option(
|
||||
"event",
|
||||
description="Name of the event",
|
||||
autocomplete=basic_autocomplete(autofill_active_events),
|
||||
autocomplete=basic_autocomplete(autocomplete_active_events),
|
||||
required=True,
|
||||
)
|
||||
async def command_event_show(self, ctx: ApplicationContext, event: str) -> None:
|
||||
|
@@ -4,7 +4,7 @@ from discord.utils import basic_autocomplete
|
||||
|
||||
from classes import PycordGuild, PycordEventStage, PycordEvent
|
||||
from classes.pycord_bot import PycordBot
|
||||
from modules.utils import autofill_active_events
|
||||
from modules.utils import autocomplete_active_events, autocomplete_event_stages
|
||||
|
||||
|
||||
class Stage(Cog):
|
||||
@@ -24,7 +24,7 @@ class Stage(Cog):
|
||||
@option(
|
||||
"event",
|
||||
description="Name of the event",
|
||||
autocomplete=basic_autocomplete(autofill_active_events),
|
||||
autocomplete=basic_autocomplete(autocomplete_active_events),
|
||||
required=True,
|
||||
)
|
||||
@option("question", description="Question to be answered", required=True)
|
||||
@@ -54,7 +54,7 @@ class Stage(Cog):
|
||||
creator_id=ctx.author.id,
|
||||
question=question,
|
||||
answer=answer,
|
||||
media=None if media is None else media.id,
|
||||
media=[] if media is None else [media.id],
|
||||
)
|
||||
|
||||
# TODO Make a nice message
|
||||
@@ -69,14 +69,21 @@ class Stage(Cog):
|
||||
@option(
|
||||
"event",
|
||||
description="Name of the event",
|
||||
autocomplete=basic_autocomplete(autofill_active_events),
|
||||
autocomplete=basic_autocomplete(autocomplete_active_events),
|
||||
required=True,
|
||||
)
|
||||
@option("stage", description="Stage to edit", required=True)
|
||||
@option("order", description="Number in the event stages' order", required=False)
|
||||
# TODO Add autofill
|
||||
@option(
|
||||
"stage",
|
||||
description="Stage to edit",
|
||||
autocomplete=basic_autocomplete(autocomplete_event_stages),
|
||||
required=True,
|
||||
)
|
||||
@option("order", description="Number in the event stages' order", min_value=1, required=False)
|
||||
@option("question", description="Question to be answered", required=False)
|
||||
@option("answer", description="Answer to the stage's question", required=False)
|
||||
@option("media", description="Media file to be attached", required=False)
|
||||
@option("remove_media", description="Remove attached media", required=False)
|
||||
async def command_stage_edit(
|
||||
self,
|
||||
ctx: ApplicationContext,
|
||||
@@ -86,8 +93,33 @@ class Stage(Cog):
|
||||
question: str,
|
||||
answer: str,
|
||||
media: Attachment = None,
|
||||
remove_media: bool = False,
|
||||
) -> None:
|
||||
await ctx.respond("Not implemented.")
|
||||
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
|
||||
pycord_event: PycordEvent = await self.bot.find_event(event)
|
||||
event_stage: PycordEventStage = await self.bot.find_event_stage(stage)
|
||||
|
||||
if not guild.is_configured():
|
||||
# TODO Make a nice message
|
||||
await ctx.respond("Guild is not configured.")
|
||||
return
|
||||
|
||||
if order is not None and order > len(pycord_event.stage_ids):
|
||||
# TODO Make a nice message
|
||||
await ctx.respond("Stage sequence out of range.")
|
||||
return
|
||||
|
||||
if not (question is None and answer is None and media is None and remove_media is False):
|
||||
await event_stage.update(
|
||||
question=event_stage.question if question is None else question,
|
||||
answer=event_stage.answer if answer is None else answer,
|
||||
media=[] if remove_media else (event_stage.media if media is None else [media.id]),
|
||||
)
|
||||
|
||||
if order is not None and order - 1 != event_stage.sequence:
|
||||
await pycord_event.reorder_stage(self.bot, event_stage._id, order - 1, cache=self.bot.cache)
|
||||
|
||||
await ctx.respond("Event stage has been updated.")
|
||||
|
||||
# TODO Implement the command
|
||||
# /stage delete <event> <stage> <confirm>
|
||||
@@ -98,15 +130,38 @@ class Stage(Cog):
|
||||
@option(
|
||||
"event",
|
||||
description="Name of the event",
|
||||
autocomplete=basic_autocomplete(autofill_active_events),
|
||||
autocomplete=basic_autocomplete(autocomplete_active_events),
|
||||
required=True,
|
||||
)
|
||||
@option(
|
||||
"stage",
|
||||
description="Stage to delete",
|
||||
autocomplete=basic_autocomplete(autocomplete_event_stages),
|
||||
required=True,
|
||||
)
|
||||
@option("stage", description="Stage to edit", required=True)
|
||||
@option("confirm", description="Confirmation of the operation", required=False)
|
||||
async def command_stage_delete(
|
||||
self, ctx: ApplicationContext, event: str, stage: str, confirm: bool = False
|
||||
) -> None:
|
||||
await ctx.respond("Not implemented.")
|
||||
if confirm is None or not confirm:
|
||||
# TODO Make a nice message
|
||||
await ctx.respond("Operation not confirmed.")
|
||||
return
|
||||
|
||||
guild: PycordGuild = await self.bot.find_guild(ctx.guild.id)
|
||||
pycord_event: PycordEvent = await self.bot.find_event(event)
|
||||
event_stage: PycordEventStage = await self.bot.find_event_stage(stage)
|
||||
|
||||
if not guild.is_configured():
|
||||
# TODO Make a nice message
|
||||
await ctx.respond("Guild is not configured.")
|
||||
return
|
||||
|
||||
await pycord_event.remove_stage(self.bot, event_stage._id, cache=self.bot.cache)
|
||||
await event_stage.purge(cache=self.bot.cache)
|
||||
|
||||
# TODO Make a nice message
|
||||
await ctx.respond("Okay.")
|
||||
|
||||
|
||||
def setup(bot: PycordBot) -> None:
|
||||
|
@@ -2,9 +2,11 @@ from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
from zoneinfo import ZoneInfo, available_timezones
|
||||
|
||||
from bson import ObjectId
|
||||
from discord import AutocompleteContext, OptionChoice
|
||||
from pymongo import ASCENDING
|
||||
|
||||
from modules.database import col_events
|
||||
from modules.database import col_events, col_stages
|
||||
|
||||
|
||||
def hex_to_int(hex_color: str) -> int:
|
||||
@@ -16,19 +18,19 @@ def int_to_hex(integer_color: int) -> str:
|
||||
|
||||
|
||||
# TODO Maybe move to a separate module
|
||||
async def autofill_timezones(ctx: AutocompleteContext) -> List[str]:
|
||||
async def autocomplete_timezones(ctx: AutocompleteContext) -> List[str]:
|
||||
return sorted(list(available_timezones()))
|
||||
|
||||
|
||||
# TODO Maybe move to a separate module
|
||||
async def autofill_languages(ctx: AutocompleteContext) -> List[str]:
|
||||
async def autocomplete_languages(ctx: AutocompleteContext) -> List[str]:
|
||||
# TODO Discord normally uses a different set of locales.
|
||||
# For example, "en" being "en-US", etc. This will require changes to locale handling later.
|
||||
return ctx.bot.locales.keys()
|
||||
|
||||
|
||||
# TODO Maybe move to a separate module
|
||||
async def autofill_active_events(ctx: AutocompleteContext) -> List[OptionChoice]:
|
||||
async def autocomplete_active_events(ctx: AutocompleteContext) -> List[OptionChoice]:
|
||||
query: Dict[str, Any] = {
|
||||
"ended": None,
|
||||
"ends": {"$gt": datetime.now(tz=ZoneInfo("UTC"))},
|
||||
@@ -41,3 +43,24 @@ async def autofill_active_events(ctx: AutocompleteContext) -> List[OptionChoice]
|
||||
event_names.append(OptionChoice(result["name"], str(result["_id"])))
|
||||
|
||||
return event_names
|
||||
|
||||
|
||||
# TODO Maybe move to a separate module
|
||||
async def autocomplete_event_stages(ctx: AutocompleteContext) -> List[OptionChoice]:
|
||||
event_id: str | None = ctx.options["event"]
|
||||
|
||||
if event_id is None:
|
||||
return []
|
||||
|
||||
query: Dict[str, Any] = {
|
||||
"event_id": ObjectId(event_id),
|
||||
}
|
||||
|
||||
event_stages: List[OptionChoice] = []
|
||||
|
||||
async for result in col_stages.find(query).sort([("sequence", ASCENDING)]):
|
||||
event_stages.append(
|
||||
OptionChoice(f"{result['sequence']+1} ({result['question']})", str(result["_id"]))
|
||||
)
|
||||
|
||||
return event_stages
|
||||
|
Reference in New Issue
Block a user