Skip to content

tux.cogs.services.bookmarks

Classes:

Name Description
Bookmarks

Classes

Bookmarks(bot: Tux)

Bases: Cog

Methods:

Name Description
cog_unload

Cleans up the cog, closing the aiohttp session.

on_raw_reaction_add

Handles bookmarking messages via reactions.

add_bookmark

Sends a bookmarked message to the user's DMs.

remove_bookmark

Deletes a bookmark DM when the user reacts with the remove emoji.

Source code in tux/cogs/services/bookmarks.py
Python
def __init__(self, bot: Tux) -> None:
    self.bot = bot
    self.add_bookmark_emojis = CONST.ADD_BOOKMARK
    self.remove_bookmark_emojis = CONST.REMOVE_BOOKMARK
    self.valid_emojis = self.add_bookmark_emojis + self.remove_bookmark_emojis
    self.session = aiohttp.ClientSession()

Functions

cog_unload() -> None async

Cleans up the cog, closing the aiohttp session.

Source code in tux/cogs/services/bookmarks.py
Python
async def cog_unload(self) -> None:
    """Cleans up the cog, closing the aiohttp session."""
    await self.session.close()
on_raw_reaction_add(payload: discord.RawReactionActionEvent) -> None async

Handles bookmarking messages via reactions.

This listener checks for specific reaction emojis on messages and triggers the bookmarking or unbookmarking process accordingly.

Parameters:

Name Type Description Default
payload RawReactionActionEvent

The event payload containing information about the reaction.

required
Source code in tux/cogs/services/bookmarks.py
Python
@commands.Cog.listener()
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None:
    """
    Handles bookmarking messages via reactions.

    This listener checks for specific reaction emojis on messages and triggers
    the bookmarking or unbookmarking process accordingly.

    Parameters
    ----------
    payload : discord.RawReactionActionEvent
        The event payload containing information about the reaction.
    """

    # If the bot reacted to the message, or the user is the bot, or the emoji is not valid, return
    if not self.bot.user or payload.user_id == self.bot.user.id or not payload.emoji.name:
        return

    # If the emoji is not valid, return
    if payload.emoji.name not in self.valid_emojis:
        return

    try:
        # Get the user who reacted to the message
        user = self.bot.get_user(payload.user_id) or await self.bot.fetch_user(payload.user_id)

        # Get the channel where the reaction was added
        channel = self.bot.get_channel(payload.channel_id)
        if channel is None:
            channel = await self.bot.fetch_channel(payload.channel_id)

        # If the channel is not messageable, return
        if not isinstance(channel, Messageable):
            logger.warning(f"Bookmark reaction in non-messageable channel {payload.channel_id}.")
            return

        # Get the message that was reacted to
        message = await channel.fetch_message(payload.message_id)

    # If the message is not found, return
    except (discord.NotFound, discord.Forbidden, discord.HTTPException) as e:
        logger.error(f"Failed to fetch data for bookmark event: {e}")
        return

    # If the emoji is the add bookmark emoji, add the bookmark
    if payload.emoji.name in self.add_bookmark_emojis:
        await self.add_bookmark(user, message)

    # If the emoji is the remove bookmark emoji, remove the bookmark
    elif payload.emoji.name in self.remove_bookmark_emojis:
        await self.remove_bookmark(message)
add_bookmark(user: discord.User, message: discord.Message) -> None async

Sends a bookmarked message to the user's DMs.

Parameters:

Name Type Description Default
user User

The user who bookmarked the message.

required
message Message

The message to be bookmarked.

required
Source code in tux/cogs/services/bookmarks.py
Python
async def add_bookmark(self, user: discord.User, message: discord.Message) -> None:
    """
    Sends a bookmarked message to the user's DMs.

    Parameters
    ----------
    user : discord.User
        The user who bookmarked the message.
    message : discord.Message
        The message to be bookmarked.
    """
    embed = self._create_bookmark_embed(message)
    files = await self._get_files_from_message(message)

    try:
        dm_message = await user.send(embed=embed, files=files)
        await dm_message.add_reaction(self.remove_bookmark_emojis)

    except (discord.Forbidden, discord.HTTPException) as e:
        logger.warning(f"Could not send DM to {user.name} ({user.id}): {e}")

        try:
            await message.channel.send(
                f"{user.mention}, I couldn't send you a DM. Please check your privacy settings.",
                delete_after=30,
            )

        except (discord.Forbidden, discord.HTTPException) as e2:
            logger.error(f"Could not send notification in channel {message.channel.id}: {e2}")
remove_bookmark(message: discord.Message) -> None async staticmethod

Deletes a bookmark DM when the user reacts with the remove emoji.

Parameters:

Name Type Description Default
message Message

The bookmark message in the user's DMs to be deleted.

required
Source code in tux/cogs/services/bookmarks.py
Python
@staticmethod
async def remove_bookmark(message: discord.Message) -> None:
    """
    Deletes a bookmark DM when the user reacts with the remove emoji.

    Parameters
    ----------
    message : discord.Message
        The bookmark message in the user's DMs to be deleted.
    """

    try:
        await message.delete()

    except (discord.Forbidden, discord.HTTPException) as e:
        logger.error(f"Failed to delete bookmark message {message.id}: {e}")
_get_files_from_message(message: discord.Message) -> list[discord.File] async

Gathers images from a message to be sent as attachments.

This function collects images from attachments, stickers, and embeds, respecting Discord's 10-file limit.

Parameters:

Name Type Description Default
message Message

The message to extract files from.

required

Returns:

Type Description
list[File]

A list of files to be attached to the bookmark message.

Source code in tux/cogs/services/bookmarks.py
Python
async def _get_files_from_message(self, message: discord.Message) -> list[discord.File]:
    """
    Gathers images from a message to be sent as attachments.

    This function collects images from attachments, stickers, and embeds,
    respecting Discord's 10-file limit.

    Parameters
    ----------
    message : discord.Message
        The message to extract files from.

    Returns
    -------
    list[discord.File]
        A list of files to be attached to the bookmark message.
    """
    files: list[discord.File] = []

    await self._get_files_from_attachments(message, files)
    await self._get_files_from_stickers(message, files)
    await self._get_files_from_embeds(message, files)

    return files
_create_bookmark_embed(message: discord.Message) -> discord.Embed

Creates an embed for a bookmarked message.

This function constructs a detailed embed that includes the message content, author, attachments, and other contextual information.

Parameters:

Name Type Description Default
message Message

The message to create an embed from.

required

Returns:

Type Description
Embed

The generated bookmark embed.

Source code in tux/cogs/services/bookmarks.py
Python
def _create_bookmark_embed(self, message: discord.Message) -> discord.Embed:
    """
    Creates an embed for a bookmarked message.

    This function constructs a detailed embed that includes the message content,
    author, attachments, and other contextual information.

    Parameters
    ----------
    message : discord.Message
        The message to create an embed from.

    Returns
    -------
    discord.Embed
        The generated bookmark embed.
    """

    # Get the content of the message
    content = message.content or ""

    # Truncate the content if it's too long
    if len(content) > CONST.EMBED_MAX_DESC_LENGTH:
        content = f"{content[: CONST.EMBED_MAX_DESC_LENGTH - 4]}..."

    embed = EmbedCreator.create_embed(
        bot=self.bot,
        embed_type=EmbedCreator.INFO,
        title="Message Bookmarked",
        description=f"{content}" if content else "> No content available to display",
    )

    # Add author to the embed
    embed.set_author(
        name=message.author.display_name,
        icon_url=message.author.display_avatar.url,
    )

    # Add reference to the embed if it exists
    if message.reference and message.reference.resolved:
        ref_msg = message.reference.resolved
        if isinstance(ref_msg, discord.Message):
            embed.add_field(
                name="Replying to",
                value=f"[Click Here]({ref_msg.jump_url})",
            )

    # Add jump to message to the embed
    embed.add_field(
        name="Jump to Message",
        value=f"[Click Here]({message.jump_url})",
    )

    # Add attachments to the embed
    if message.attachments:
        attachments = "\n".join(f"[{a.filename}]({a.url})" for a in message.attachments)
        embed.add_field(name="Attachments", value=attachments, inline=False)

    # Add stickers to the embed
    if message.stickers:
        stickers = "\n".join(f"[{s.name}]({s.url})" for s in message.stickers)
        embed.add_field(name="Stickers", value=stickers, inline=False)

    # Handle embeds
    if message.embeds:
        embed.add_field(
            name="Contains Embeds",
            value="Original message contains embeds which are not shown here.",
            inline=False,
        )

    # Add footer to the embed
    if message.guild and isinstance(message.channel, discord.TextChannel | discord.Thread):
        embed.set_footer(text=f"In #{message.channel.name} on {message.guild.name}")

    # Add timestamp to the embed
    embed.timestamp = message.created_at

    return embed