v1.0.0 #15
@@ -189,6 +189,12 @@ class PycordBot(LibPycordBot):
|
|||||||
self._("admin_event_started", "messages").format(event_name=event.name),
|
self._("admin_event_started", "messages").format(event_name=event.name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await self.notify_users(
|
||||||
|
guild,
|
||||||
|
pycord_guild,
|
||||||
|
self._("event_started", "messages").format(event_name=event.name),
|
||||||
|
)
|
||||||
|
|
||||||
async def _process_events_end(self) -> None:
|
async def _process_events_end(self) -> None:
|
||||||
# Get events to end
|
# Get events to end
|
||||||
events: List[PycordEvent] = await self._get_events(
|
events: List[PycordEvent] = await self._get_events(
|
||||||
@@ -228,6 +234,17 @@ class PycordBot(LibPycordBot):
|
|||||||
# Get list of participants
|
# Get list of participants
|
||||||
users: List[PycordUser] = await self._get_event_participants(event._id)
|
users: List[PycordUser] = await self._get_event_participants(event._id)
|
||||||
|
|
||||||
|
event_ended_string: str = self._("event_ended", "messages").format(
|
||||||
|
event_name=event.name, stages=stages_string
|
||||||
|
)
|
||||||
|
|
||||||
|
chunk_size: int = 2000
|
||||||
|
|
||||||
|
event_info_chunks: List[str] = [
|
||||||
|
event_ended_string[i : i + chunk_size]
|
||||||
|
for i in range(0, len(event_ended_string), chunk_size)
|
||||||
|
]
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
if str(event._id) not in user.event_channels:
|
if str(event._id) not in user.event_channels:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -240,30 +257,58 @@ class PycordBot(LibPycordBot):
|
|||||||
# Send a notification about event start
|
# Send a notification about event start
|
||||||
user_channel: TextChannel = guild.get_channel(user.event_channels[str(event._id)])
|
user_channel: TextChannel = guild.get_channel(user.event_channels[str(event._id)])
|
||||||
|
|
||||||
event_ended_string: str = self._("event_ended", "messages").format(
|
|
||||||
event_name=event.name, stages=stages_string
|
|
||||||
)
|
|
||||||
|
|
||||||
chunk_size: int = 2000
|
|
||||||
|
|
||||||
event_info_chunks: List[str] = [
|
|
||||||
event_ended_string[i : i + chunk_size]
|
|
||||||
for i in range(0, len(event_ended_string), chunk_size)
|
|
||||||
]
|
|
||||||
|
|
||||||
for chunk in event_info_chunks:
|
for chunk in event_info_chunks:
|
||||||
await user_channel.send(chunk)
|
await user_channel.send(chunk)
|
||||||
|
|
||||||
# Lock each participant out
|
# Lock each participant out
|
||||||
await user.lock_event_channel(guild, event._id, channel=user_channel)
|
await user.lock_event_channel(guild, event._id, channel=user_channel)
|
||||||
|
|
||||||
|
await event.end(cache=self.cache)
|
||||||
|
|
||||||
await self.notify_admins(
|
await self.notify_admins(
|
||||||
guild,
|
guild,
|
||||||
pycord_guild,
|
pycord_guild,
|
||||||
self._("admin_event_ended", "messages").format(event_name=event.name),
|
self._("admin_event_ended", "messages").format(event_name=event.name),
|
||||||
)
|
)
|
||||||
|
|
||||||
await event.end(cache=self.cache)
|
await self._notify_general_channel_event_end(guild, pycord_guild, event, stages)
|
||||||
|
|
||||||
|
async def _notify_general_channel_event_end(
|
||||||
|
self, guild: Guild, pycord_guild: PycordGuild, event: PycordEvent, stages: List[PycordEventStage]
|
||||||
|
) -> None:
|
||||||
|
event_ended_string: str = self._("event_ended_short", "messages").format(event_name=event.name)
|
||||||
|
|
||||||
|
await self.notify_users(
|
||||||
|
guild,
|
||||||
|
pycord_guild,
|
||||||
|
event_ended_string,
|
||||||
|
)
|
||||||
|
|
||||||
|
chunk_size: int = 2000
|
||||||
|
|
||||||
|
for stage in stages:
|
||||||
|
header_full: str = self._("stage_entry_header", "messages").format(
|
||||||
|
sequence=stage.sequence + 1, question=stage.question
|
||||||
|
)
|
||||||
|
|
||||||
|
header_chunks: List[str] = [
|
||||||
|
header_full[i : i + chunk_size] for i in range(0, len(header_full), chunk_size)
|
||||||
|
]
|
||||||
|
header_chunks_length: int = len(header_chunks)
|
||||||
|
|
||||||
|
files: List[File] | None = stage.get_media_files()
|
||||||
|
|
||||||
|
for index, chunk in enumerate(header_chunks):
|
||||||
|
await self.notify_users(
|
||||||
|
guild,
|
||||||
|
pycord_guild,
|
||||||
|
chunk,
|
||||||
|
files=None if index != header_chunks_length - 1 else files,
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.notify_users(
|
||||||
|
guild, pycord_guild, self._("stage_entry_footer", "messages").format(answer=stage.answer)
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _get_events(query: Dict[str, Any]) -> List[PycordEvent]:
|
async def _get_events(query: Dict[str, Any]) -> List[PycordEvent]:
|
||||||
@@ -290,18 +335,35 @@ class PycordBot(LibPycordBot):
|
|||||||
# TODO Add documentation
|
# TODO Add documentation
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def notify_admins(guild: Guild, pycord_guild: PycordGuild, message: str) -> None:
|
async def notify_admins(guild: Guild, pycord_guild: PycordGuild, message: str) -> None:
|
||||||
management_channel: TextChannel | None = guild.get_channel(pycord_guild.channel_id)
|
management_channel: TextChannel | None = guild.get_channel(pycord_guild.management_channel_id)
|
||||||
|
|
||||||
if management_channel is None:
|
if management_channel is None:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Discord channel with ID %s in guild with ID %s could not be found!",
|
"Discord channel with ID %s in guild with ID %s could not be found!",
|
||||||
pycord_guild.channel_id,
|
pycord_guild.management_channel_id,
|
||||||
guild.id,
|
guild.id,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await management_channel.send(message)
|
await management_channel.send(message)
|
||||||
|
|
||||||
|
# TODO Add documentation
|
||||||
|
@staticmethod
|
||||||
|
async def notify_users(
|
||||||
|
guild: Guild, pycord_guild: PycordGuild, message: str, files: Optional[List[File]] = None
|
||||||
|
) -> None:
|
||||||
|
general_channel: TextChannel | None = guild.get_channel(pycord_guild.general_channel_id)
|
||||||
|
|
||||||
|
if general_channel is None:
|
||||||
|
logger.error(
|
||||||
|
"Discord channel with ID %s in guild with ID %s could not be found!",
|
||||||
|
pycord_guild.general_channel_id,
|
||||||
|
guild.id,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await general_channel.send(message, files=files)
|
||||||
|
|
||||||
async def find_user(self, user: int | User, guild: int | Guild) -> PycordUser:
|
async def find_user(self, user: int | User, guild: int | Guild) -> PycordUser:
|
||||||
"""Find User by its ID or User object.
|
"""Find User by its ID or User object.
|
||||||
|
|
||||||
|
@@ -17,13 +17,22 @@ logger: Logger = get_logger(__name__)
|
|||||||
class PycordGuild:
|
class PycordGuild:
|
||||||
"""Dataclass of DB entry of a guild"""
|
"""Dataclass of DB entry of a guild"""
|
||||||
|
|
||||||
__slots__ = ("_id", "id", "channel_id", "category_id", "timezone", "prefer_emojis")
|
__slots__ = (
|
||||||
|
"_id",
|
||||||
|
"id",
|
||||||
|
"general_channel_id",
|
||||||
|
"management_channel_id",
|
||||||
|
"category_id",
|
||||||
|
"timezone",
|
||||||
|
"prefer_emojis",
|
||||||
|
)
|
||||||
__short_name__ = "guild"
|
__short_name__ = "guild"
|
||||||
__collection__ = col_guilds
|
__collection__ = col_guilds
|
||||||
|
|
||||||
_id: ObjectId
|
_id: ObjectId
|
||||||
id: int
|
id: int
|
||||||
channel_id: int | None
|
general_channel_id: int | None
|
||||||
|
management_channel_id: int | None
|
||||||
category_id: int | None
|
category_id: int | None
|
||||||
timezone: str
|
timezone: str
|
||||||
prefer_emojis: bool
|
prefer_emojis: bool
|
||||||
@@ -131,7 +140,8 @@ class PycordGuild:
|
|||||||
return {
|
return {
|
||||||
"_id": self._id if not json_compatible else str(self._id),
|
"_id": self._id if not json_compatible else str(self._id),
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"channel_id": self.channel_id,
|
"general_channel_id": self.general_channel_id,
|
||||||
|
"management_channel_id": self.management_channel_id,
|
||||||
"category_id": self.category_id,
|
"category_id": self.category_id,
|
||||||
"timezone": self.timezone,
|
"timezone": self.timezone,
|
||||||
"prefer_emojis": self.prefer_emojis,
|
"prefer_emojis": self.prefer_emojis,
|
||||||
@@ -146,7 +156,8 @@ class PycordGuild:
|
|||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
"id": guild_id,
|
"id": guild_id,
|
||||||
"channel_id": None,
|
"general_channel_id": None,
|
||||||
|
"management_channel_id": None,
|
||||||
"category_id": None,
|
"category_id": None,
|
||||||
"timezone": "UTC",
|
"timezone": "UTC",
|
||||||
"prefer_emojis": False,
|
"prefer_emojis": False,
|
||||||
@@ -222,7 +233,8 @@ class PycordGuild:
|
|||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
(self.id is not None)
|
(self.id is not None)
|
||||||
and (self.channel_id is not None)
|
and (self.general_channel_id is not None)
|
||||||
|
and (self.management_channel_id is not None)
|
||||||
and (self.category_id is not None)
|
and (self.category_id is not None)
|
||||||
and (self.timezone is not None)
|
and (self.timezone is not None)
|
||||||
and (self.prefer_emojis is not None)
|
and (self.prefer_emojis is not None)
|
||||||
|
@@ -60,11 +60,6 @@ class PycordUser:
|
|||||||
registered_event_ids: List[ObjectId]
|
registered_event_ids: List[ObjectId]
|
||||||
completed_event_ids: List[ObjectId]
|
completed_event_ids: List[ObjectId]
|
||||||
|
|
||||||
# TODO Review the redesign
|
|
||||||
# event_channel_ids: {
|
|
||||||
# "%event_id%": %channel_id%
|
|
||||||
# }
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_id(
|
async def from_id(
|
||||||
cls, user_id: int, guild_id: int, allow_creation: bool = True, cache: Optional[Cache] = None
|
cls, user_id: int, guild_id: int, allow_creation: bool = True, cache: Optional[Cache] = None
|
||||||
@@ -288,7 +283,7 @@ class PycordUser:
|
|||||||
raise DiscordGuildMemberNotFoundError(self.id, guild.id)
|
raise DiscordGuildMemberNotFoundError(self.id, guild.id)
|
||||||
|
|
||||||
if discord_category is None:
|
if discord_category is None:
|
||||||
raise DiscordCategoryNotFoundError(pycord_guild.channel_id, guild.id)
|
raise DiscordCategoryNotFoundError(pycord_guild.category_id, guild.id)
|
||||||
|
|
||||||
permission_overwrites: Dict[Role | Member, PermissionOverwrite] = {
|
permission_overwrites: Dict[Role | Member, PermissionOverwrite] = {
|
||||||
guild.default_role: PermissionOverwrite(
|
guild.default_role: PermissionOverwrite(
|
||||||
|
@@ -44,10 +44,18 @@ class CogConfig(Cog):
|
|||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
@option(
|
@option(
|
||||||
"channel",
|
"general_channel",
|
||||||
description=_("description", "commands", "config_set", "options", "channel"),
|
description=_("description", "commands", "config_set", "options", "general_channel"),
|
||||||
description_localizations=in_every_locale(
|
description_localizations=in_every_locale(
|
||||||
"description", "commands", "config_set", "options", "channel"
|
"description", "commands", "config_set", "options", "general_channel"
|
||||||
|
),
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
@option(
|
||||||
|
"management_channel",
|
||||||
|
description=_("description", "commands", "config_set", "options", "management_channel"),
|
||||||
|
description_localizations=in_every_locale(
|
||||||
|
"description", "commands", "config_set", "options", "management_channel"
|
||||||
),
|
),
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
@@ -72,7 +80,8 @@ class CogConfig(Cog):
|
|||||||
self,
|
self,
|
||||||
ctx: ApplicationContext,
|
ctx: ApplicationContext,
|
||||||
category: CategoryChannel,
|
category: CategoryChannel,
|
||||||
channel: TextChannel,
|
general_channel: TextChannel,
|
||||||
|
management_channel: TextChannel,
|
||||||
timezone: str,
|
timezone: str,
|
||||||
prefer_emojis: bool,
|
prefer_emojis: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -92,7 +101,8 @@ class CogConfig(Cog):
|
|||||||
|
|
||||||
await guild.update(
|
await guild.update(
|
||||||
self.bot.cache,
|
self.bot.cache,
|
||||||
channel_id=channel.id,
|
general_channel_id=general_channel.id,
|
||||||
|
management_channel_id=management_channel.id,
|
||||||
category_id=category.id,
|
category_id=category.id,
|
||||||
timezone=str(timezone_parsed),
|
timezone=str(timezone_parsed),
|
||||||
prefer_emojis=prefer_emojis,
|
prefer_emojis=prefer_emojis,
|
||||||
@@ -145,7 +155,8 @@ class CogConfig(Cog):
|
|||||||
|
|
||||||
await ctx.respond(
|
await ctx.respond(
|
||||||
self.bot._("config_show", "messages", locale=ctx.locale).format(
|
self.bot._("config_show", "messages", locale=ctx.locale).format(
|
||||||
channel_id=guild.channel_id,
|
general_channel_id=guild.general_channel_id,
|
||||||
|
management_channel_id=guild.management_channel_id,
|
||||||
category_id=guild.category_id,
|
category_id=guild.category_id,
|
||||||
timezone=guild.timezone,
|
timezone=guild.timezone,
|
||||||
prefer_emojis=guild.prefer_emojis,
|
prefer_emojis=guild.prefer_emojis,
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
"admin_user_completed_stage": "User **{display_name}** ({mention}) has completed the stage {stage_sequence} of the event **{event_name}**.",
|
"admin_user_completed_stage": "User **{display_name}** ({mention}) has completed the stage {stage_sequence} of the event **{event_name}**.",
|
||||||
"config_reset": "Configuration has been reset. You can update it using `/config set`, otherwise no events can be held.",
|
"config_reset": "Configuration has been reset. You can update it using `/config set`, otherwise no events can be held.",
|
||||||
"config_set": "Configuration has been updated. You can review it anytime using `/config show`.",
|
"config_set": "Configuration has been updated. You can review it anytime using `/config show`.",
|
||||||
"config_show": "**Guild config**\n\nChannel: <#{channel_id}>\nCategory: <#{category_id}>\nTimezone: `{timezone}`\nPrefer emojis: `{prefer_emojis}`",
|
"config_show": "**Guild config**\n\nCategory: <#{category_id}>\nGeneral channel: <#{general_channel_id}>\nManagement channel: <#{management_channel_id}>\nTimezone: `{timezone}`\nPrefer emojis: `{prefer_emojis}`",
|
||||||
"event_cancelled": "Event **{event_name}** was cancelled.",
|
"event_cancelled": "Event **{event_name}** was cancelled.",
|
||||||
"event_created": "Event **{event_name}** has been created and will take place <t:{start_time}:R>.",
|
"event_created": "Event **{event_name}** has been created and will take place <t:{start_time}:R>.",
|
||||||
"event_dates_parsing_failed": "Could not parse start and end dates. Please, make sure these are provided in `DD.MM.YYYY HH:MM` format.",
|
"event_dates_parsing_failed": "Could not parse start and end dates. Please, make sure these are provided in `DD.MM.YYYY HH:MM` format.",
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"event_end_date_parsing_failed": "Could not parse the end date. Please, make sure it is provided in `DD.MM.YYYY HH:MM` format.",
|
"event_end_date_parsing_failed": "Could not parse the end date. Please, make sure it is provided in `DD.MM.YYYY HH:MM` format.",
|
||||||
"event_end_past": "End date must not be in the past",
|
"event_end_past": "End date must not be in the past",
|
||||||
"event_ended": "Event **{event_name}** has ended! Stages and respective answers are listed below.\n\n{stages}",
|
"event_ended": "Event **{event_name}** has ended! Stages and respective answers are listed below.\n\n{stages}",
|
||||||
|
"event_ended_short": "Event **{event_name}** has ended! Stages and respective answers are listed below.",
|
||||||
"event_is_cancelled": "This event was cancelled.",
|
"event_is_cancelled": "This event was cancelled.",
|
||||||
"event_is_starting": "Event **{event_name}** is starting!\n\nUse slash command `/guess` to suggest your answers to each event stage.",
|
"event_is_starting": "Event **{event_name}** is starting!\n\nUse slash command `/guess` to suggest your answers to each event stage.",
|
||||||
"event_name_duplicate": "There can only be one active event with the same name",
|
"event_name_duplicate": "There can only be one active event with the same name",
|
||||||
@@ -28,8 +29,9 @@
|
|||||||
"event_ongoing_not_editable": "Ongoing events cannot be modified.",
|
"event_ongoing_not_editable": "Ongoing events cannot be modified.",
|
||||||
"event_start_date_parsing_failed": "Could not parse the start date. Please, make sure it is provided in `DD.MM.YYYY HH:MM` format.",
|
"event_start_date_parsing_failed": "Could not parse the start date. Please, make sure it is provided in `DD.MM.YYYY HH:MM` format.",
|
||||||
"event_start_past": "Start date must not be in the past",
|
"event_start_past": "Start date must not be in the past",
|
||||||
|
"event_started": "Event **{event_name}** has started! Use command `/register` to participate in the event.",
|
||||||
"event_updated": "Event **{event_name}** has been updated and will take place <t:{start_time}:R>.",
|
"event_updated": "Event **{event_name}** has been updated and will take place <t:{start_time}:R>.",
|
||||||
"guess_completed_event": "Congratulations! You have completed the event!",
|
"guess_completed_event": "Congratulations! You have completed the event!\nPlease, do not share the answers with others until the event ends so that everyone can have fun. Thank you!",
|
||||||
"guess_incorrect": "Provided answer is wrong.",
|
"guess_incorrect": "Provided answer is wrong.",
|
||||||
"guess_incorrect_channel": "Usage outside own event channel is not allowed.",
|
"guess_incorrect_channel": "Usage outside own event channel is not allowed.",
|
||||||
"guess_incorrect_event": "Your event could not be found. Please, contact the administrator.",
|
"guess_incorrect_event": "Your event could not be found. Please, contact the administrator.",
|
||||||
@@ -45,6 +47,8 @@
|
|||||||
"stage_created": "Event stage has been created.",
|
"stage_created": "Event stage has been created.",
|
||||||
"stage_deleted": "Event stage has been deleted.",
|
"stage_deleted": "Event stage has been deleted.",
|
||||||
"stage_entry": "**Stage {sequence}**\nAnswer: ||{answer}||",
|
"stage_entry": "**Stage {sequence}**\nAnswer: ||{answer}||",
|
||||||
|
"stage_entry_footer": "Answer: ||{answer}||",
|
||||||
|
"stage_entry_header": "**Stage {sequence}**\nQuestion: {question}",
|
||||||
"stage_not_found": "Event stage was not found.",
|
"stage_not_found": "Event stage was not found.",
|
||||||
"stage_sequence_out_of_range": "Stage sequence out of range.",
|
"stage_sequence_out_of_range": "Stage sequence out of range.",
|
||||||
"stage_updated": "Event stage has been updated.",
|
"stage_updated": "Event stage has been updated.",
|
||||||
@@ -70,7 +74,10 @@
|
|||||||
"category": {
|
"category": {
|
||||||
"description": "Category where channels for each user will be created"
|
"description": "Category where channels for each user will be created"
|
||||||
},
|
},
|
||||||
"channel": {
|
"general_channel": {
|
||||||
|
"description": "Text channel for general notifications and bot usage"
|
||||||
|
},
|
||||||
|
"management_channel": {
|
||||||
"description": "Text channel for admin notifications"
|
"description": "Text channel for admin notifications"
|
||||||
},
|
},
|
||||||
"timezone": {
|
"timezone": {
|
||||||
|
Reference in New Issue
Block a user