diff --git a/.gitignore b/.gitignore index 8863534..0b15ce9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /target -Cargo.lock +*.lock .env -.st* \ No newline at end of file +.st* +.DS_Store +.idea diff --git a/interactions/src/commands/base.py b/interactions/src/commands/base.py index ad53726..ca54fd8 100644 --- a/interactions/src/commands/base.py +++ b/interactions/src/commands/base.py @@ -117,6 +117,15 @@ async def message_plural_debug( ) )]) +@message_command( + name='/plu/ral delete', + contexts=InteractionContextType.ALL(), + integration_types=ApplicationIntegrationType.ALL()) +async def message_plural_delete( + interaction: Interaction, + message: Message +) -> None: + await PAGES['delete'](interaction, message) @message_command( name='/plu/ral edit', @@ -569,6 +578,49 @@ async def slash_delete_all_data( await PAGES['delete_all_data'](interaction) +@slash_command( + name='delete', + description='Delete your most recent message', + contexts=InteractionContextType.ALL(), + integration_types=ApplicationIntegrationType.ALL() +) +async def slash_delete( + interaction: Interaction +) -> None: + db_message = await DBMessage.find_one( + {'user': (await interaction.get_usergroup()).id, + 'channel_id': interaction.channel_id}, + sort=[('ts', -1)] + ) + + if db_message is None: + raise InteractionError('No messages found to delete') + + if db_message.interaction_token and db_message.expired: + raise InteractionError( + 'No message found\n\n' + 'Messages older than 15 minutes cannot be deleted' + ) + + try: + message = await ( + Webhook.from_db_message( + db_message + ).fetch_message( + db_message.proxy_id) + if db_message.interaction_token else + Message.fetch( + interaction.channel_id, + db_message.proxy_id)) + except Forbidden as e: + raise InteractionError( + 'Unable to read messages in this channel, please use the /plu/ral delete message command' + '\n\n(right-click on the message -> Apps -> /plu/ral delete)' + ) from e + + await PAGES['delete'](interaction, message) + + @slash_command( name='edit', description='Edit your most recent message', diff --git a/interactions/src/commands/helpers.py b/interactions/src/commands/helpers.py index 68d163b..340760b 100644 --- a/interactions/src/commands/helpers.py +++ b/interactions/src/commands/helpers.py @@ -1,6 +1,6 @@ from datetime import timedelta from asyncio import gather -from typing import Any +from typing import Any, Never from beanie import PydanticObjectId from regex import ( @@ -271,10 +271,98 @@ async def edit_message( )] ) +async def forbidden_error( + action: str, + *reasons: str, + src: Exception | None = None, +) -> Never: + if len(reasons) == 0: + reasons = ( + 'it is either not a /plu/ral message, older than 7 days, ', + 'or you are not the author of the message', + ) + if src is None: + raise InteractionError( + f"You cannot {action} this message, ", + *reasons + ) + raise InteractionError( + f"You cannot {action} this message, ", + *reasons + ) from src + + +async def can_delete( + interaction: Interaction, + message: Message, +) -> None: + db_message = await DBMessage.find_one({ + 'user': (await interaction.get_usergroup()).id, + 'channel_id': interaction.channel_id, + '$or': [ + {'original_id': message.id}, + {'proxy_id': message.id} + ] + }) + + if db_message is None: + await forbidden_error('delete') + + match db_message.reason: + case 'Userproxy /proxy command' | 'Userproxy Reply command': + userproxy = await ProxyMember.find_one({ + 'userproxy.bot_id': message.author.id + }) + + if userproxy is None: + raise InteractionError('Userproxy not found') + + usergroup = await interaction.get_usergroup() + + group = await userproxy.get_group() + + if not ( + usergroup.id == group.account or + interaction.author_id in group.users + ): + await forbidden_error( + 'delete' + ) + + if not db_message.expired: + return + + try: + await Channel.fetch( + message.channel_id, + userproxy.userproxy.token) + except HTTPException as e: + await forbidden_error( + 'delete', + 'Userproxy messages can be deleted normally', + src=e + ) + case '/say command' if ( + message.interaction_metadata and + message.interaction_metadata.user.id == interaction.author_id + ): + return + case _ if message.webhook_id: + if message.webhook_id != db_message.webhook_id: + await forbidden_error( + 'delete' + ) + case _ if message.author.bot: + pass + case _: + await forbidden_error( + 'delete' + ) + async def can_edit( interaction: Interaction, - message: Message + message: Message, ) -> None: db_message = await DBMessage.find_one({ 'user': (await interaction.get_usergroup()).id, @@ -286,11 +374,7 @@ async def can_edit( }) if db_message is None: - raise InteractionError( - 'You cannot edit this message, ' - 'it is either not a /plu/ral message, older than 7 days, ' - 'or you are not the author of the message' - ) + await forbidden_error('edit') match db_message.reason: case 'Userproxy /proxy command' | 'Userproxy Reply command': @@ -309,10 +393,8 @@ async def can_edit( usergroup.id == group.account or interaction.author_id in group.users ): - raise InteractionError( - 'You cannot edit this message, ' - 'it is either not a /plu/ral message, older than 7 days, ' - 'or you are not the author of the message' + await forbidden_error( + 'edit' ) if not db_message.expired: @@ -323,11 +405,12 @@ async def can_edit( message.channel_id, userproxy.userproxy.token) except HTTPException as e: - raise InteractionError( - 'You cannot edit this message, ' + await forbidden_error( + 'edit', 'Userproxy messages can only be edited within 15 minutes' - ' of sending them unless the userproxy bot is in the server' - ) from e + ' of sending them unless the userproxy bot is in the server', + src=e + ) case '/say command' if ( message.interaction_metadata and message.interaction_metadata.user.id == interaction.author_id @@ -337,16 +420,12 @@ async def can_edit( if message.webhook_id != db_message.webhook_id: raise InteractionError( 'You cannot edit this message, ' - 'it is either not a /plu/ral message, older than 7 days, ' - 'or you are not the author of the message' ) case _ if message.author.bot: pass case _: - raise InteractionError( - 'You cannot edit this message, ' - 'it is either not a /plu/ral message, older than 7 days, ' - 'or you are not the author of the message' + await forbidden_error( + 'edit' ) diff --git a/interactions/src/components/__init__.py b/interactions/src/components/__init__.py index 77f213a..fd4bebd 100644 --- a/interactions/src/components/__init__.py +++ b/interactions/src/components/__init__.py @@ -4,6 +4,7 @@ from .proxy import PAGES as PROXY_PAGES from .help import PAGES as HELP_PAGES from .base import PAGES as BASE_PAGES +from .delete import PAGES as DELETE_PAGES from .edit import PAGES as EDIT_PAGES from .api import PAGES as API_PAGES from .bio import PAGES as BIO_PAGES @@ -23,6 +24,7 @@ PROXY_PAGES | HELP_PAGES | BASE_PAGES | + DELETE_PAGES | EDIT_PAGES | API_PAGES | BIO_PAGES | diff --git a/interactions/src/components/delete.py b/interactions/src/components/delete.py new file mode 100644 index 0000000..180b703 --- /dev/null +++ b/interactions/src/components/delete.py @@ -0,0 +1,50 @@ +from plural.db import Message as DBMessage, redis + +from src.commands.helpers import make_json_safe + +from src.discord import ( + Message, + Interaction +) + + +PAGES = { + 'delete': lambda interaction, message: _delete(interaction, message) +} + +async def _delete( + interaction: Interaction, + message: Message +) -> None: + from src.commands.helpers import can_delete + + await can_delete(interaction, message) + + db_message = await DBMessage.find_one({ + 'user': (await interaction.get_usergroup()).id, + 'channel_id': interaction.channel_id, + '$or': [ + {'original_id': message.id}, + {'proxy_id': message.id} + ] + }) + + if db_message is None: + raise RuntimeError( + 'DBMessage not found but delete check passed.\n\n' + 'this should never happen' + ) + + pipeline = redis.pipeline() + pipeline.json().set( + f'discord:pending_delete:{message.id}', '$', + make_json_safe( + message.model_dump( + mode='json', + exclude_defaults=True))) + pipeline.expire(f'discord:pending_delete:{message.id}', 900) + await pipeline.execute() + + await message.delete() + + await interaction.response.ack()