Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
700bb40
Create bookmarks.py
malcontent5 Aug 29, 2024
b9c797a
wip changes
malcontent5 Jun 9, 2025
89691c3
Merge branch 'main' of https://github.com/cherryl1k/tux
malcontent5 Jun 9, 2025
38c22a5
Merge branch 'allthingslinux:main' into main
malcontent5 Jun 19, 2025
56bebaf
Added removing bookmarks from the bot's DMs
malcontent5 Jun 19, 2025
217c364
fix(bookmarks): improve emoji validation and error handling for user …
electron271 Jun 19, 2025
b8a4072
added eletrons changes and fixed a warning
malcontent5 Jun 19, 2025
f16d0f9
Merge branch 'main' of https://github.com/cherryl1k/tux
malcontent5 Jun 19, 2025
34798e9
i think i fixed whatever the hell git just did
malcontent5 Jun 19, 2025
e9119ab
Merge branch 'allthingslinux:main' into main
malcontent5 Jun 19, 2025
72d3054
Merge branch 'allthingslinux:main' into main
malcontent5 Jun 19, 2025
b547dd4
Merge branch 'main' of https://github.com/cherryl1k/tux
malcontent5 Jun 20, 2025
0b4d6d9
Merge branch 'allthingslinux:main' into main
malcontent5 Jun 20, 2025
ba2c381
chore(wip): still working on debugging
malcontent5 Jun 20, 2025
1c5f20e
wip changes
malcontent5 Jun 9, 2025
bdb6103
Added removing bookmarks from the bot's DMs
malcontent5 Jun 19, 2025
66f4df9
added eletrons changes and fixed a warning
malcontent5 Jun 19, 2025
b5dcbb7
fix(bookmarks): improve emoji validation and error handling for user …
electron271 Jun 19, 2025
2ce131a
chore(wip): still working on debugging
malcontent5 Jun 20, 2025
5d36f15
Merge branch 'main' of https://github.com/cherryl1k/tux
malcontent5 Jun 20, 2025
6a7918c
Merge branch 'allthingslinux:main' into bookmarks
malcontent5 Jun 20, 2025
aacfee8
wip changes
malcontent5 Jun 9, 2025
b7cf0f4
Added removing bookmarks from the bot's DMs
malcontent5 Jun 19, 2025
67110f5
added eletrons changes and fixed a warning
malcontent5 Jun 19, 2025
5c959c7
fix(bookmarks): improve emoji validation and error handling for user …
electron271 Jun 19, 2025
f1e4c72
chore(wip): still working on debugging
malcontent5 Jun 20, 2025
627c5bb
wip changes
malcontent5 Jun 9, 2025
000bc72
Added removing bookmarks from the bot's DMs
malcontent5 Jun 19, 2025
dc15ffe
added eletrons changes and fixed a warning
malcontent5 Jun 19, 2025
38f9809
fix(bookmarks): improve emoji validation and error handling for user …
electron271 Jun 19, 2025
fc56203
i think i fixed whatever the hell git just did
malcontent5 Jun 19, 2025
8477862
chore(wip): still working on debugging
malcontent5 Jun 20, 2025
36a5db6
chore(wip): still working on debugging
malcontent5 Jun 20, 2025
db0d571
feat:(bookmarks) cleaned up removing bookmarks
malcontent5 Jun 20, 2025
8ceef67
fix:(bookmarks) removed redundent error check
malcontent5 Jun 20, 2025
502cc5d
chore: update Python version from 3.13.2 to 3.13.5 in documentation
kzndotsh Jun 21, 2025
7fa2ee0
Merge branch 'allthingslinux:main' into bookmarks
malcontent5 Jun 21, 2025
e0148cd
Merge branch 'main' of https://github.com/allthingslinux/tux
malcontent5 Jun 21, 2025
d7542e0
Create bookmarks.py
malcontent5 Aug 29, 2024
c5e51ac
wip changes
malcontent5 Jun 9, 2025
18e4e34
Added removing bookmarks from the bot's DMs
malcontent5 Jun 19, 2025
bc36aba
added eletrons changes and fixed a warning
malcontent5 Jun 19, 2025
23ace84
fix(bookmarks): improve emoji validation and error handling for user …
electron271 Jun 19, 2025
e8c16a0
i think i fixed whatever the hell git just did
malcontent5 Jun 19, 2025
0a672c6
chore(wip): still working on debugging
malcontent5 Jun 20, 2025
fbe4d0f
chore(wip): still working on debugging
malcontent5 Jun 20, 2025
47f5a9b
feat:(bookmarks) cleaned up removing bookmarks
malcontent5 Jun 20, 2025
9a3f537
fix:(bookmarks) removed redundent error check
malcontent5 Jun 20, 2025
0a5e043
Merge branch 'bookmarks' of https://github.com/cherryl1k/tux into boo…
malcontent5 Jun 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions cogs/utility/bookmarks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import discord
from discord.ext import commands
from loguru import logger

from tux.utils.embeds import EmbedCreator


class Bookmarks(commands.Cog):
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot

@commands.Cog.listener()
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Add guard clause (last-if-guard)

if str(payload.emoji) == "🔖":
try:
channel = self.bot.get_channel(payload.channel_id)
if channel is None:
logger.error("Channel not found")
return

message = await channel.fetch_message(payload.message_id)
if message is None:
logger.error("Message not found")
return

embed = EmbedCreator.create_info_embed(
title="Message Bookmarked",
description=f"> {message.content}",
)
embed.add_field(name="Author", value=message.author.name, inline=False)
embed.add_field(name="Jump to Message", value=f"[Click Here]({message.jump_url})", inline=False)

if message.attachments:
attachments_info = "\n".join([attachment.url for attachment in message.attachments])
embed.add_field(name="Attachments", value=attachments_info, inline=False)

try:
user = self.bot.get_user(payload.user_id)
if user is not None:
await user.send(embed=embed)
await message.remove_reaction(payload.emoji, user)
else:
logger.error(f"User not found for ID: {payload.user_id}")
except (discord.Forbidden, discord.HTTPException):
logger.error(f"Cannot send a DM to {user.name}. They may have DMs turned off.")
await message.remove_reaction(payload.emoji, user)
temp_message = await channel.send(
f"{user.mention}, I couldn't send you a DM make sure your DMs are open for bookmarks to work",
)
await temp_message.delete(delay=30)
except (discord.NotFound, discord.Forbidden, discord.HTTPException) as e:
logger.error(f"Failed to process the reaction: {e}")


async def setup(bot: commands.Bot) -> None:
await bot.add_cog(Bookmarks(bot))
62 changes: 46 additions & 16 deletions tux/cogs/services/bookmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
def __init__(self, bot: Tux) -> None:
self.bot = bot

self.valid_add_emojis = CONST.ADD_BOOKMARK
self.valid_remove_emojis = CONST.REMOVE_BOOKMARK
self.valid_emojis = CONST.ADD_BOOKMARK + CONST.REMOVE_BOOKMARK

Check warning on line 18 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L16-L18

Added lines #L16 - L18 were not covered by tests

# The linter wants to change this but it breaks when it does that
def _is_valid_emoji(self, emoji: discord.PartialEmoji, valid_list: str) -> bool:

Check warning on line 21 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L21

Added line #L21 was not covered by tests
if emoji.name in valid_list: # noqa: SIM103
return True
return False

Check warning on line 24 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L23-L24

Added lines #L23 - L24 were not covered by tests
Comment on lines +22 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): We've found these issues:

Suggested change
if emoji.name in valid_list: # noqa: SIM103
return True
return False
return emoji.name in valid_list


@commands.Cog.listener()
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring on_raw_reaction_add to flatten control flow, centralize fetch logic, and make embed creation synchronous.

You can flatten `on_raw_reaction_add` and remove unreachable branches by:

1. Dropping the initial `valid_emojis` check and final `else` (it’s never reached once you branch by add/remove).
2. Pulling user/channel get‐then‐fetch logic into small helpers.
3. Making `_create_bookmark_embed` a sync method again (nothing inside is async).

Example refactor:

```python
class Bookmarks(commands.Cog):
    def __init__(…):
        …
        self.add_emojis = set(CONST.ADD_BOOKMARK)
        self.remove_emojis = set(CONST.REMOVE_BOOKMARK)

    async def on_raw_reaction_add(self, payload):
        emoji = payload.emoji.name
        if emoji in self.add_emojis:
            user = await self._get_user(payload.user_id) or return
            channel = await self._get_channel(payload.channel_id) or return
            message = await self._safe_fetch(channel.fetch_message, payload.message_id) or return
            embed = self._create_bookmark_embed(message)
            await self._send_bookmark(user, message, embed)
        elif emoji in self.remove_emojis:
            user = await self._get_user(payload.user_id) or return
            channel = await self._get_channel(payload.channel_id) or return
            message = await self._safe_fetch(channel.fetch_message, payload.message_id) or return
            await self._delete_bookmark(message)
        # no final else needed

    async def _get_user(self, user_id):
        user = self.bot.get_user(user_id)
        if user: return user
        return await self._safe_fetch(self.bot.fetch_user, user_id)

    async def _get_channel(self, channel_id):
        channel = self.bot.get_channel(channel_id)
        if channel: return channel
        return await self._safe_fetch(self.bot.fetch_channel, channel_id)

    async def _safe_fetch(self, fn, *args):
        try:
            return await fn(*args)
        except discord.NotFound:
            logger.error(f"Not found: {fn.__name__}({args[0]})")
        except (discord.Forbidden, discord.HTTPException) as e:
            logger.error(f"Failed {fn.__name__}: {e}")

    def _create_bookmark_embed(self, message: discord.Message) -> discord.Embed:
        content = message.content
        if len(content) > CONST.EMBED_MAX_DESC_LENGTH:
            content = content[:CONST.EMBED_MAX_DESC_LENGTH - 3] + "..."
        embed = EmbedCreator.create_embed(
            bot=self.bot,
            embed_type=EmbedCreator.INFO,
            title="Message Bookmarked",
            description=f"> {content}",
        )
        embed.add_field("Author", message.author.name, inline=False)
        embed.add_field("Jump to Message", f"[Click Here]({message.jump_url})", inline=False)
        if message.attachments:
            urls = "\n".join(a.url for a in message.attachments)
            embed.add_field("Attachments", urls, inline=False)
        return embed

This removes deep nesting, centralizes fetch-and-log, drops the “how’d you get here?” branch, and keeps embed creation synchronous.

"""
Expand All @@ -27,15 +37,30 @@
-------
None
"""

if str(payload.emoji) != "🔖":
if not self._is_valid_emoji(payload.emoji, self.valid_emojis):
return

# Get the user who reacted to the message
user = self.bot.get_user(payload.user_id)

Check warning on line 44 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L44

Added line #L44 was not covered by tests
if user is None:
try:
user = await self.bot.fetch_user(payload.user_id)
except discord.NotFound:
logger.error(f"User not found for ID: {payload.user_id}")
except (discord.Forbidden, discord.HTTPException) as fetch_error:
logger.error(f"Failed to fetch user: {fetch_error}")
return

Check warning on line 52 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L46-L52

Added lines #L46 - L52 were not covered by tests
# Fetch the channel where the reaction was added
channel = self.bot.get_channel(payload.channel_id)
if channel is None:
logger.error(f"Channel not found for ID: {payload.channel_id}")
try:
channel = await self.bot.fetch_channel(payload.channel_id)
except discord.NotFound:
logger.error(f"Channel not found for ID: {payload.channel_id}")
except (discord.Forbidden, discord.HTTPException) as fetch_error:
logger.error(f"Failed to fetch channel: {fetch_error}")

Check warning on line 61 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L56-L61

Added lines #L56 - L61 were not covered by tests
return

channel = cast(discord.TextChannel | discord.Thread, channel)

# Fetch the message that was reacted to
Expand All @@ -48,19 +73,20 @@
logger.error(f"Failed to fetch message: {fetch_error}")
return

# Create an embed for the bookmarked message
embed = self._create_bookmark_embed(message)
# check for what to do
if self._is_valid_emoji(payload.emoji, self.valid_add_emojis):
# Create an embed for the bookmarked message
embed = await self._create_bookmark_embed(message)

Check warning on line 79 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L79

Added line #L79 was not covered by tests

# Get the user who reacted to the message
user = self.bot.get_user(payload.user_id)
if user is None:
logger.error(f"User not found for ID: {payload.user_id}")
return
# Send the bookmarked message to the user
await self._send_bookmark(user, message, embed, payload.emoji)

Check warning on line 82 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L82

Added line #L82 was not covered by tests

# Send the bookmarked message to the user
await self._send_bookmark(user, message, embed, payload.emoji)
elif self._is_valid_emoji(payload.emoji, self.valid_remove_emojis) and user is not self.bot.user:
await self._delete_bookmark(message, user)

Check warning on line 85 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L85

Added line #L85 was not covered by tests
else:
return

Check warning on line 87 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L87

Added line #L87 was not covered by tests

def _create_bookmark_embed(
async def _create_bookmark_embed(

Check warning on line 89 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L89

Added line #L89 was not covered by tests
self,
message: discord.Message,
) -> discord.Embed:
Expand All @@ -77,13 +103,16 @@
embed.add_field(name="Author", value=message.author.name, inline=False)

embed.add_field(name="Jump to Message", value=f"[Click Here]({message.jump_url})", inline=False)

if message.attachments:
attachments_info = "\n".join([attachment.url for attachment in message.attachments])
embed.add_field(name="Attachments", value=attachments_info, inline=False)

return embed

async def _delete_bookmark(self, message: discord.Message, user: discord.User) -> None:

Check warning on line 111 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L111

Added line #L111 was not covered by tests
if message.author is not self.bot.user:
return
await message.delete()

Check warning on line 114 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L113-L114

Added lines #L113 - L114 were not covered by tests

@staticmethod
async def _send_bookmark(
user: discord.User,
Expand All @@ -107,7 +136,8 @@
"""

try:
await user.send(embed=embed)
dm_message = await user.send(embed=embed)
await dm_message.add_reaction(CONST.REMOVE_BOOKMARK)

Check warning on line 140 in tux/cogs/services/bookmarks.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/services/bookmarks.py#L139-L140

Added lines #L139 - L140 were not covered by tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Adding a reaction to the DM message may fail if the bot lacks permissions.

Handle potential exceptions from add_reaction with a try/except block to prevent unhandled errors if the bot lacks permissions or DMs are restricted.


except (discord.Forbidden, discord.HTTPException) as dm_error:
logger.error(f"Cannot send a DM to {user.name}: {dm_error}")
Expand Down
18 changes: 11 additions & 7 deletions tux/cogs/utility/poll.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import cast

Check warning on line 1 in tux/cogs/utility/poll.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/utility/poll.py#L1

Added line #L1 was not covered by tests

import discord
from discord import app_commands
from discord.ext import commands
Expand Down Expand Up @@ -74,13 +76,15 @@
# get reaction from payload.message_id, payload.channel_id, payload.guild_id, payload.emoji
channel = self.bot.get_channel(payload.channel_id)
if channel is None:
logger.error(f"Channel with ID {payload.channel_id} not found.")
return
if isinstance(channel, discord.ForumChannel | discord.CategoryChannel | discord.abc.PrivateChannel):
logger.error(
f"Channel with ID {payload.channel_id} is not a compatible channel type. How the fuck did you get here?",
)
return
try:
channel = await self.bot.fetch_channel(payload.channel_id)
except discord.NotFound:
logger.error(f"Channel not found for ID: {payload.channel_id}")
return
except (discord.Forbidden, discord.HTTPException) as fetch_error:
logger.error(f"Failed to fetch channel: {fetch_error}")
return
channel = cast(discord.TextChannel | discord.Thread, channel)

Check warning on line 87 in tux/cogs/utility/poll.py

View check run for this annotation

Codecov / codecov/patch

tux/cogs/utility/poll.py#L79-L87

Added lines #L79 - L87 were not covered by tests

message = await channel.fetch_message(payload.message_id)
# Lookup the reaction object for this event
Expand Down
4 changes: 4 additions & 0 deletions tux/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,9 @@ class Constants:
EIGHT_BALL_QUESTION_LENGTH_LIMIT = 120
EIGHT_BALL_RESPONSE_WRAP_WIDTH = 30

# Bookmark constants
ADD_BOOKMARK = "🔖"
REMOVE_BOOKMARK = "🗑️"


CONST = Constants()
Loading