From 7f131172eb72677418303c85c5d892fc11cc963e Mon Sep 17 00:00:00 2001 From: Steele Farnsworth Date: Sun, 12 Jan 2025 17:41:47 -0500 Subject: work-in-progress: when upload a text file attachment, ask for permission to auto-upload to pastebin. Also DMs the delete URL to the user. This code will very likely be moved elsewhere before/if it is merged. --- bot/exts/filtering/_filter_lists/extension.py | 92 ++++++++++++++++++++++++++- bot/exts/filtering/filtering.py | 2 + 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/bot/exts/filtering/_filter_lists/extension.py b/bot/exts/filtering/_filter_lists/extension.py index d656bc6d2..3e7fa8e75 100644 --- a/bot/exts/filtering/_filter_lists/extension.py +++ b/bot/exts/filtering/_filter_lists/extension.py @@ -1,10 +1,18 @@ 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.constants import Channels +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 from bot.exts.filtering._filters.extension import ExtensionFilter @@ -20,7 +28,7 @@ PY_EMBED_DESCRIPTION = ( f"please use a code-pasting service such as {PASTE_URL}" ) -TXT_LIKE_FILES = {".txt", ".csv", ".json"} +TXT_LIKE_FILES = {".txt", ".csv", ".json", ".py"} TXT_EMBED_DESCRIPTION = ( "You either uploaded a `{blocked_extension}` file or entered a message that was too long. " f"Please use our [paste bin]({PASTE_URL}) instead." @@ -32,6 +40,9 @@ DISALLOWED_EMBED_DESCRIPTION = ( "Feel free to ask in {meta_channel_mention} if you think this is a mistake." ) +PASTEBIN_UPLOAD_EMOJI = Emojis.check_mark +DELETE_PASTE_EMOJI = Emojis.trashcan + class ExtensionsList(FilterList[ExtensionFilter]): """ @@ -116,3 +127,80 @@ 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 844f2942e..929eb064c 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -28,6 +28,7 @@ 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 @@ -1492,3 +1493,4 @@ 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)) -- cgit v1.2.3