Skip to content

feat: adding attachments to snippets. - #3421

Open
lorenzo132 wants to merge 13 commits into
developmentfrom
users/lorenzo/snippet-attachment-support
Open

feat: adding attachments to snippets.#3421
lorenzo132 wants to merge 13 commits into
developmentfrom
users/lorenzo/snippet-attachment-support

Conversation

@lorenzo132

Copy link
Copy Markdown
Member

This adds the ability to add attachments to snippets.

This adds the ability to add attachments to snippets
@StephenDaDev

Copy link
Copy Markdown
Member

I have began a review which will result in a "changes requested" verdict. I will need additional time to complete a full review. I hope to have it completed by sometime around 12PM tomorrow, Eastern.

@martinbndr martinbndr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

image

Is it intented, that an image is sent seperate comparing to the normal reply? If not maybe we should consider letting images being added to the embed aswell to have it consistent to the replying?

@lorenzo132

lorenzo132 commented Dec 23, 2025

Copy link
Copy Markdown
Member Author
image Is it intented, that an image is sent seperate comparing to the normal reply? If not maybe we should consider letting images being added to the embed aswell to have it consistent to the replying?

I have changed the behavior, now it just sets the attachment.(I was half asleep when i made this lolololol)

Copilot AI lite review requested due to automatic review settings August 2, 2026 11:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for storing, managing, and sending file attachments associated with snippets, including persisting snippet attachments in MongoDB GridFS and relaying them through the existing thread send pipeline.

Changes:

  • Persist snippet attachments in MongoDB GridFS with upload/download/delete APIs and a configurable max attachment size.
  • Extend snippet commands (snippet add/edit/remove) to accept/manage an optional attachment and display attachment presence in snippet views.
  • Attach downloaded snippet files to outgoing thread messages (including embedding snippet images via attachment://).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
core/thread.py Adds files_to_upload pipeline and special handling for embedded snippet images when sending thread messages.
core/config.py Introduces snippet_attachment_max_size config key and conversion handling for MB-based values.
core/config_help.json Documents the new snippet_attachment_max_size config option.
core/clients.py Adds GridFS bucket + upload/download/delete methods for snippet attachments.
cogs/modmail.py Updates snippet CRUD commands to support attachments (validation, confirmation, GridFS persistence).
bot.py Downloads snippet attachments at invocation time and wraps them to flow through existing attachment sending logic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core/thread.py
Comment thread cogs/modmail.py
Comment thread cogs/modmail.py Outdated
Comment thread cogs/modmail.py Outdated
Comment thread bot.py Outdated
Comment thread core/thread.py Outdated
Copilot AI review requested due to automatic review settings August 2, 2026 11:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (4)

core/thread.py:2020

  • ext includes forwarded attachments (added just above), but the loop only iterates over message.attachments, so forwarded attachments appended to ext are never classified into images/attachments and won't be included in the outgoing embed/log.
        for i, a in enumerate(message.attachments):
            attachment = ext[i]
            if getattr(a, "is_snippet_attachment", False):

bot.py:1413

  • This replaces the original message attachments with only the snippet attachment. If a user invokes a snippet while also attaching files, those files will be silently dropped.
            if attachment is not None:
                snippet_message = copy.copy(message)
                snippet_message.attachments = [attachment]
                ctx.message = snippet_message

bot.py:1386

  • This overwrites any attachments present on the original alias-invoking message when a snippet attachment exists, so user-provided attachments would be dropped instead of being sent along with the snippet attachment.

This issue also appears on line 1410 of the same file.

                        attachment = await self._download_snippet_attachment(snippet_data)
                        if attachment is not None:
                            context_message.attachments = [attachment]
                    else:

cogs/modmail.py:363

  • The help text hard-codes a 10 MB limit, but the actual limit is configurable via snippet_attachment_max_size (default 10). This can mislead users if the config is changed.
        You can also attach a file (max 10 MB) to include with the snippet.

Copilot AI review requested due to automatic review settings August 2, 2026 11:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

cogs/modmail.py:399

  • confirm_msg is only defined inside the if ctx.message.attachments: block, but it’s referenced later unconditionally (if confirm_msg:). When adding a text-only snippet (no attachment), this will raise an UnboundLocalError.
        # Handle optional attachment
        file_id = None
        attachment_info = None
        if ctx.message.attachments:

cogs/modmail.py:363

  • The docstring hard-codes “max 10 MB”, but the actual limit is configurable via snippet_attachment_max_size (default 10). This will become inaccurate if the config is changed.
        You can also attach a file (max 10 MB) to include with the snippet.

Copilot AI review requested due to automatic review settings August 2, 2026 13:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (2)

cogs/modmail.py:399

  • confirm_msg is only defined inside the if ctx.message.attachments: block, but it is referenced later (if confirm_msg:). When creating a snippet without an attachment, this will raise UnboundLocalError at runtime.
        # Handle optional attachment
        file_id = None
        attachment_info = None
        if ctx.message.attachments:

cogs/modmail.py:363

  • The help text hard-codes a 10 MB maximum, but the actual limit is configurable via snippet_attachment_max_size (default 10). This makes the command help inaccurate when the config is changed.
        You can also attach a file (max 10 MB) to include with the snippet.

Copilot AI review requested due to automatic review settings August 4, 2026 13:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (4)

bot.py:1385

  • This overwrites any attachments that were present on the original message when an alias expands to a snippet with an attachment. Since downstream sending code is already written to handle mixes of snippet and non-snippet attachments, consider appending the snippet attachment instead of replacing the list.
                            context_message.attachments = [attachment]

bot.py:1412

  • This overwrites any attachments on the original message when invoking a snippet that has an attachment, which can unexpectedly drop user-provided files. Consider appending the snippet attachment to the existing attachments list instead.
                snippet_message.attachments = [attachment]

bot.py:1393

  • command_invocation_text already gets prefixed with invoked_prefix when constructing StringView(invoked_prefix + command_invocation_text). Including invoked_prefix inside command_invocation_text duplicates the prefix (e.g. !!!!freply ...) and can prevent the alias-expanded context from parsing the intended command.
                    command_invocation_text = f"{invoked_prefix}{command} {snippet_text}"

cogs/modmail.py:363

  • The docstring hard-codes a 10 MB maximum, but the actual limit is configurable via snippet_attachment_max_size (and may not be 10). Update the wording so it stays accurate when the config changes.
        You can also attach a file (max 10 MB) to include with the snippet.

@lorenzo132 lorenzo132 closed this Aug 4, 2026
auto-merge was automatically disabled August 4, 2026 15:42

Pull request was closed

@lorenzo132 lorenzo132 reopened this Aug 4, 2026

@StephenDaDev StephenDaDev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

tested working, no serious issues found

@martinbndr martinbndr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good.

@sebkuip sebkuip left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review for now. Did not check all, and is purely a code review. Have to do some testing later myself and look further into the code.

Comment thread cogs/modmail.py
snippet_text = self._get_snippet_text(snippet_data)
has_attachment = self._has_snippet_attachment(snippet_data)

description = snippet_text if snippet_text else "(No text content)"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If a snippet is only an attachment, having it show up as (No text content) This snippet has an attachment seems weird. I would put it as something like this is an attachment-only snippet

Comment thread cogs/modmail.py

display_value = return_or_truncate(snippet_text, 350) if snippet_text else "(No text)"
if has_attachment:
display_value = "📎 " + display_value

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe make the embed have a footer text explaining that the paperclip emoji means the embed has an attachment?

Comment thread cogs/modmail.py

description = snippet_text if snippet_text else "(No text content)"
if has_attachment:
description += "\n\n📎 *This snippet has an attachment.*"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd say show the attachment when you request the snippet. In the list it's fine to leave out but if you just pull up that snippet it should show.

Comment thread cogs/modmail.py
val = truncate(escape_code_block(snippet_text), 2048 - 7) if snippet_text else "(No text content)"
description = f"```\n{val}```"
if has_attachment:
description += "\n\n📎 *This snippet has an attachment.*"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Similar to the regular version of this command, show the attachment so the staff member can see what it sends.

Comment thread cogs/modmail.py
file_id = None
attachment_info = None
if ctx.message.attachments:
attachment = ctx.message.attachments[0]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don't silently ignore multiple attachments. Either allow it, or give the user an error that only a single attachment is allowed.

Comment thread cogs/modmail.py
return await ctx.send(embed=embed)

# Require at least text or attachment
if not value and not file_id:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This if statement acts as a guard, and should be at the top of the method. Probably change it to if not value and len(message.attachments) == 0:

Comment thread cogs/modmail.py
self.bot.snippets.pop(name)
await self.bot.config.update()
if file_id and not await self.bot.api.delete_snippet_attachment(file_id):
logger.warning("Failed to delete snippet attachment for %s.", name)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should either inform the user in chat, or fail the command entirely so you don't end up with tons of data on gridFS when it errors a few times.

Comment thread cogs/modmail.py
```

Attach a new file to replace the existing attachment.
Provide text without attachment to keep the existing attachment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I feel like an edit should be able to remove the attachment as well. Isn't it better that providing no attachment deletes it, instead of having the user needing to delete the snippet if they want the attachment gone?

Comment thread cogs/utility.py

def process_help_msg(self, help_: str):
return help_.format(prefix=self.context.clean_prefix) if help_ else "No help message."
return help_.replace("{prefix}", self.context.clean_prefix) if help_ else "No help message."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this randomly edited? This is out of scope for the PR

Comment thread core/config.py
elif key in self.megabytes:
if not isinstance(value, int):
try:
value = int(value)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Might also want to guard against negative values. Also this causes rounding when a comma value is entered, is that intentional?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants