aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Steele Farnsworth <[email protected]>2025-01-29 19:33:02 -0500
committerGravatar Steele Farnsworth <[email protected]>2025-01-29 19:33:02 -0500
commit9f4177e33c5ba118c90505868d009ab7b46561d5 (patch)
treeaf24598e4876674d8d02e69793187d57d8fd0356
parentAdd helper function for extracting attachment text. (diff)
Move EmbedFileHandler cog to its own module
-rw-r--r--bot/exts/filtering/_filter_lists/extension.py85
-rw-r--r--bot/exts/filtering/filtering.py2
-rw-r--r--bot/exts/utils/attachment_pastebin_uploader.py111
3 files changed, 111 insertions, 87 deletions
diff --git a/bot/exts/filtering/_filter_lists/extension.py b/bot/exts/filtering/_filter_lists/extension.py
index 3e7fa8e75..a89a980a7 100644
--- a/bot/exts/filtering/_filter_lists/extension.py
+++ b/bot/exts/filtering/_filter_lists/extension.py
@@ -1,17 +1,9 @@
from __future__ import annotations
-import logging
-import re
import typing
from os.path import splitext
-import aiohttp
-import discord
-from discord.ext import commands
-from pydis_core.utils import paste_service
-
import bot
-from bot.bot import Bot
from bot.constants import Channels, Emojis
from bot.exts.filtering._filter_context import Event, FilterContext
from bot.exts.filtering._filter_lists.filter_list import FilterList, ListType
@@ -127,80 +119,3 @@ class ExtensionsList(FilterList[ExtensionFilter]):
ctx.blocked_exts |= set(not_allowed)
actions = self[ListType.ALLOW].defaults.actions if ctx.event != Event.SNEKBOX else None
return actions, [f"`{ext}`" if ext else "`No Extension`" for ext in not_allowed], {ListType.ALLOW: triggered}
-
-
-class EmbedFileHandler(commands.Cog):
-
- def __init__(self, bot: Bot):
- self.bot = bot
-
- @staticmethod
- async def _convert_attachment(attachment: discord.Attachment) -> paste_service.PasteFile:
- encoding = re.search(r"charset=(\S+)", attachment.content_type).group(1)
- file_content = (await attachment.read()).decode(encoding)
- return paste_service.PasteFile(content=file_content, name=attachment.filename)
-
- @commands.Cog.listener()
- async def on_message(self, message: discord.Message) -> None:
- # Check if the message contains an embedded file and is not sent by a bot
- if message.author.bot or not message.attachments:
- return
-
- bot_reply = await message.reply(f"React with {PASTEBIN_UPLOAD_EMOJI} to upload your file to our paste bin")
- await bot_reply.add_reaction(PASTEBIN_UPLOAD_EMOJI)
-
- def wait_for_upload_permission(reaction: discord.Reaction, user: discord.User) -> bool:
- return (
- reaction.message.id == bot_reply.id
- and str(reaction.emoji) == PASTEBIN_UPLOAD_EMOJI
- and user == message.author
- )
-
- try:
- # Wait for the reaction with a timeout of 60 seconds
- await self.bot.wait_for("reaction_add", timeout=60.0, check=wait_for_upload_permission)
- except TimeoutError:
- await bot_reply.edit(content=f"~~{bot_reply.content}~~")
- await bot_reply.clear_reactions()
- return
-
- logging.info({f.filename: f.content_type for f in message.attachments})
-
- files = [
- await self._convert_attachment(f)
- for f in message.attachments
- if f.content_type.startswith("text")
- ]
-
- try:
- async with aiohttp.ClientSession() as session:
- paste_response = await paste_service.send_to_paste_service(files=files, http_session=session)
- except (paste_service.PasteTooLongError, ValueError):
- # paste is too long
- await bot_reply.edit(content="Your paste is too long, and couldn't be uploaded.")
- return
- except paste_service.PasteUploadError:
- await bot_reply.edit(content="There was an error uploading your paste.")
- return
-
- # The angle brackets around the remove link are required to stop Discord from visiting the URL to produce a
- # preview, thereby deleting the paste
- await message.author.send(content=f"[Click here](<{paste_response.removal}>) to delete your recent paste.")
-
- await bot_reply.edit(content=f"[Click here]({paste_response.link}) to see this code in our pastebin.")
- await bot_reply.clear_reactions()
- await bot_reply.add_reaction(DELETE_PASTE_EMOJI)
-
- def wait_for_delete_reaction(reaction: discord.Reaction, user: discord.User) -> bool:
- return (
- reaction.message.id == bot_reply.id
- and str(reaction.emoji) == DELETE_PASTE_EMOJI
- and user == message.author
- )
-
- try:
- await self.bot.wait_for("reaction_add", timeout=60.0 * 10, check=wait_for_delete_reaction)
- await paste_response.delete()
- await bot_reply.delete()
- except TimeoutError:
- pass
diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py
index f902ee9ec..e1483e18f 100644
--- a/bot/exts/filtering/filtering.py
+++ b/bot/exts/filtering/filtering.py
@@ -28,7 +28,6 @@ from bot.constants import BaseURLs, Channels, Guild, MODERATION_ROLES, Roles
from bot.exts.backend.branding._repository import HEADERS, PARAMS
from bot.exts.filtering._filter_context import Event, FilterContext
from bot.exts.filtering._filter_lists import FilterList, ListType, ListTypeConverter, filter_list_types
-from bot.exts.filtering._filter_lists.extension import EmbedFileHandler
from bot.exts.filtering._filter_lists.filter_list import AtomicList
from bot.exts.filtering._filters.filter import Filter, UniqueFilter
from bot.exts.filtering._settings import ActionSettings
@@ -1509,4 +1508,3 @@ class Filtering(Cog):
async def setup(bot: Bot) -> None:
"""Load the Filtering cog."""
await bot.add_cog(Filtering(bot))
- await bot.add_cog(EmbedFileHandler(bot))
diff --git a/bot/exts/utils/attachment_pastebin_uploader.py b/bot/exts/utils/attachment_pastebin_uploader.py
new file mode 100644
index 000000000..cc507f39e
--- /dev/null
+++ b/bot/exts/utils/attachment_pastebin_uploader.py
@@ -0,0 +1,111 @@
+from __future__ import annotations
+
+import logging
+import re
+
+import aiohttp
+import discord
+from discord.ext import commands
+from pydis_core.utils import paste_service
+
+from bot.bot import Bot
+from bot.constants import Emojis
+
+PASTEBIN_UPLOAD_EMOJI = Emojis.check_mark
+DELETE_PASTE_EMOJI = Emojis.trashcan
+
+
+class EmbedFileHandler(commands.Cog):
+ """
+ Handles automatic uploading of attachments to the paste bin.
+
+ Whenever a user uploads one or more attachments that is text-based (py, txt, csv, etc.), this cog offers to upload
+ all the attachments to the paste bin automatically. The steps are as follows:
+ - The bot replies to the message containing the attachments, asking the user to react with a checkmark to consent
+ to having the content uploaded.
+ - If consent is given, the bot uploads the contents and edits its own message to contain the link.
+ - The bot DMs the user the delete link for the paste.
+ - The bot waits for the user to react with a trashcan emoji, in which case the bot deletes the paste and its own
+ message.
+ """
+
+ def __init__(self, bot: Bot):
+ self.bot = bot
+
+ @staticmethod
+ async def _convert_attachment(attachment: discord.Attachment) -> paste_service.PasteFile:
+ """Converts an attachment to a PasteFile, according to the attachment's file encoding."""
+ encoding = re.search(r"charset=(\S+)", attachment.content_type).group(1)
+ file_content = (await attachment.read()).decode(encoding)
+ return paste_service.PasteFile(content=file_content, name=attachment.filename)
+
+ @commands.Cog.listener()
+ async def on_message(self, message: discord.Message) -> None:
+ """Listens for messages containing attachments and offers to upload them to the pastebin."""
+ # Check if the message contains an embedded file and is not sent by a bot
+ if message.author.bot or not message.attachments:
+ return
+
+ bot_reply = await message.reply(f"React with {PASTEBIN_UPLOAD_EMOJI} to upload your file to our paste bin")
+ await bot_reply.add_reaction(PASTEBIN_UPLOAD_EMOJI)
+
+ def wait_for_upload_permission(reaction: discord.Reaction, user: discord.User) -> bool:
+ return (
+ reaction.message.id == bot_reply.id
+ and str(reaction.emoji) == PASTEBIN_UPLOAD_EMOJI
+ and user == message.author
+ )
+
+ try:
+ # Wait for the reaction with a timeout of 60 seconds
+ await self.bot.wait_for("reaction_add", timeout=60.0, check=wait_for_upload_permission)
+ except TimeoutError:
+ await bot_reply.edit(content=f"~~{bot_reply.content}~~")
+ await bot_reply.clear_reactions()
+ return
+
+ logging.info({f.filename: f.content_type for f in message.attachments})
+
+ files = [
+ await self._convert_attachment(f)
+ for f in message.attachments
+ if f.content_type.startswith("text")
+ ]
+
+ try:
+ async with aiohttp.ClientSession() as session:
+ paste_response = await paste_service.send_to_paste_service(files=files, http_session=session)
+ except (paste_service.PasteTooLongError, ValueError):
+ # paste is too long
+ await bot_reply.edit(content="Your paste is too long, and couldn't be uploaded.")
+ return
+ except paste_service.PasteUploadError:
+ await bot_reply.edit(content="There was an error uploading your paste.")
+ return
+
+ # The angle brackets around the remove link are required to stop Discord from visiting the URL to produce a
+ # preview, thereby deleting the paste
+ await message.author.send(content=f"[Click here](<{paste_response.removal}>) to delete your recent paste.")
+
+ await bot_reply.edit(content=f"[Click here]({paste_response.link}) to see this code in our pastebin.")
+ await bot_reply.clear_reactions()
+ await bot_reply.add_reaction(DELETE_PASTE_EMOJI)
+
+ def wait_for_delete_reaction(reaction: discord.Reaction, user: discord.User) -> bool:
+ return (
+ reaction.message.id == bot_reply.id
+ and str(reaction.emoji) == DELETE_PASTE_EMOJI
+ and user == message.author
+ )
+
+ try:
+ await self.bot.wait_for("reaction_add", timeout=60.0 * 10, check=wait_for_delete_reaction)
+ await paste_response.delete()
+ await bot_reply.delete()
+ except TimeoutError:
+ pass
+
+
+async def setup(bot: Bot) -> None:
+ """Load the EmbedFileHandler cog."""
+ await bot.add_cog(EmbedFileHandler(bot))