From e86a873f29d3a570abe6807ee6a4ee87f5d8f4f8 Mon Sep 17 00:00:00 2001 From: Tejas <98106526+ToxicBiohazard@users.noreply.github.com> Date: Wed, 27 Nov 2024 09:08:21 +0000 Subject: [PATCH 1/3] Spoilers are directed to the Jira Tests are authored by Jelle Co-authored-by: Jelle Janssens --- .test.env | 3 +- src/cmds/core/other.py | 81 +++++++++++++++++++----------- src/core/config.py | 3 +- tests/src/cmds/core/test_other.py | 82 ++++++++++++++++--------------- 4 files changed, 99 insertions(+), 70 deletions(-) diff --git a/.test.env b/.test.env index a581c6b..918654f 100644 --- a/.test.env +++ b/.test.env @@ -87,5 +87,4 @@ CURRENT_SEASON_ID=1 HTB_API_KEY=CHANGE_ME #Feedback Webhook - -SLACK_WEBHOOK="https://hook.slack.com/sdfsdfsf" \ No newline at end of file +JIRA_SPOILER_WEBHOOK="https://automation.atlassian.com/sdfsdfsf" \ No newline at end of file diff --git a/src/cmds/core/other.py b/src/cmds/core/other.py index 6983b39..e4be8ea 100644 --- a/src/cmds/core/other.py +++ b/src/cmds/core/other.py @@ -1,7 +1,8 @@ import logging +import aiohttp import discord -from discord import ApplicationContext, Embed, Interaction, Message, Option, WebhookMessage, slash_command +from discord import ApplicationContext, Interaction, Message, Option, slash_command from discord.ext import commands from discord.ui import InputText, Modal from slack_sdk.webhook import WebhookClient @@ -16,16 +17,16 @@ class FeedbackModal(Modal): """Feedback modal.""" def __init__(self, *args, **kwargs) -> None: + """Initialize the Feedback Modal with input fields.""" super().__init__(*args, **kwargs) - self.add_item(InputText(label="Title")) self.add_item(InputText(label="Feedback", style=discord.InputTextStyle.long)) async def callback(self, interaction: discord.Interaction) -> None: - """Callback for the feedback modal.""" + """Handle the modal submission by sending feedback to Slack.""" await interaction.response.send_message("Thank you, your feedback has been recorded.", ephemeral=True) - webhook = WebhookClient(settings.SLACK_WEBHOOK) # Establish Slack Webhook + webhook = WebhookClient(settings.SLACK_FEEDBACK_WEBHOOK) if interaction.user: # Protects against some weird edge-cases title = f"{self.children[0].value} - {interaction.user.name}" @@ -53,21 +54,53 @@ async def callback(self, interaction: discord.Interaction) -> None: assert response.body == "ok" +class SpoilerModal(Modal): + """Modal for reporting a spoiler.""" + + def __init__(self, *args, **kwargs) -> None: + """Initialize the Spoiler Modal with input fields.""" + super().__init__(*args, **kwargs) + self.add_item(InputText(label="URL", placeholder="Enter the spoiler URL", style=discord.InputTextStyle.long)) + + async def callback(self, interaction: discord.Interaction) -> None: + """Handle the modal submission by sending the spoiler report to JIRA.""" + url = self.children[0].value.strip() # Trim any whitespace + + if not url: # Check if the URL is empty + await interaction.response.send_message("Please provide the spoiler URL.", ephemeral=True) + return + await interaction.response.send_message("Thank you, the spoiler has been reported.", ephemeral=True) + + user_name = interaction.user.display_name + url = self.children[0].value + + webhook_url = settings.JIRA_SPOILER_WEBHOOK + + payload = { + "user": user_name, + "url": url, + } + + async with aiohttp.ClientSession() as session: + try: + async with session.post(webhook_url, json=payload) as response: + if response.status != 200: + logger.error(f"Failed to send to JIRA: {response.status} - {await response.text()}") + except Exception as e: + logger.error(f"Error sending to JIRA: {e}") + + class OtherCog(commands.Cog): - """Ban related commands.""" + """Other commands related to the bot.""" def __init__(self, bot: Bot): self.bot = bot @slash_command(guild_ids=settings.guild_ids, description="A simple reply stating hints are not allowed.") - async def no_hints( - self, ctx: ApplicationContext - ) -> Message: - """A simple reply stating hints are not allowed.""" + async def no_hints(self, ctx: ApplicationContext) -> Message: + """Reply stating that hints are not allowed.""" return await ctx.respond( - "No hints are allowed for the duration the event is going on. This is a competitive event with prizes. " - "Once the event is over you are more then welcome to share solutions/write-ups/etc and try them in the " - "After Party event." + "No hints are allowed for the duration of the event. Once the event is over, feel free to share solutions." ) @slash_command(guild_ids=settings.guild_ids, @@ -86,28 +119,20 @@ async def support( "https://help.hackthebox.com/en/articles/5986762-contacting-htb-support" ) - @slash_command(guild_ids=settings.guild_ids, description="Add the URL which has spoiler link.") - async def spoiler(self, ctx: ApplicationContext, url: str) -> Interaction | WebhookMessage: - """Add the URL which has spoiler link.""" - if len(url) == 0: - return await ctx.respond("Please provide the spoiler URL.") - - embed = Embed(title="Spoiler Report", color=0xB98700) - embed.add_field(name=f"{ctx.user} has submitted a spoiler.", value=f"URL: <{url}>", inline=False) - - channel = self.bot.get_channel(settings.channels.SPOILER) - await channel.send(embed=embed) - return await ctx.respond("Thanks for the reporting the spoiler.", ephemeral=True, delete_after=15) + @slash_command(guild_ids=settings.guild_ids, description="Add a URL which contains a spoiler.") + async def spoiler(self, ctx: ApplicationContext) -> Interaction: + """Report a URL that contains a spoiler.""" + modal = SpoilerModal(title="Report Spoiler") + return await ctx.send_modal(modal) - @slash_command(guild_ids=settings.guild_ids, description="Provide feedback to HTB!") + @slash_command(guild_ids=settings.guild_ids, description="Provide feedback to HTB.") @commands.cooldown(1, 60, commands.BucketType.user) async def feedback(self, ctx: ApplicationContext) -> Interaction: - """Provide Feedback to HTB.""" - # Send the Modal defined above in Feedback Modal, which handles the callback + """Provide feedback to HTB.""" modal = FeedbackModal(title="Feedback") return await ctx.send_modal(modal) def setup(bot: Bot) -> None: - """Load the `ChannelManageCog` cog.""" + """Load the OtherCog cog.""" bot.add_cog(OtherCog(bot)) diff --git a/src/core/config.py b/src/core/config.py index 17da102..5fa8baf 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -187,7 +187,8 @@ class Global(BaseSettings): WEBHOOK_PORT: int = 1337 WEBHOOK_TOKEN: str = "" - SLACK_WEBHOOK: str = "" + SLACK_FEEDBACK_WEBHOOK: str = "" + JIRA_SPOILER_WEBHOOK: str = "" ROOT: Path = None diff --git a/tests/src/cmds/core/test_other.py b/tests/src/cmds/core/test_other.py index dfa226d..7a47c84 100644 --- a/tests/src/cmds/core/test_other.py +++ b/tests/src/cmds/core/test_other.py @@ -1,9 +1,12 @@ -from unittest.mock import patch +from unittest.mock import AsyncMock, patch import pytest +from discord import ApplicationContext +from src.bot import Bot from src.cmds.core import other -from tests.helpers import MockTextChannel +from src.cmds.core.other import OtherCog, SpoilerModal +from src.core import settings class TestOther: @@ -12,7 +15,7 @@ class TestOther: @pytest.mark.asyncio async def test_no_hints(self, bot, ctx): """Test the response of the `no_hints` command.""" - cog = other.OtherCog(bot) + cog = OtherCog(bot) ctx.bot = bot # Invoke the command. @@ -80,45 +83,46 @@ async def test_support_urls_different(self, bot, ctx): assert labs_url != academy_url @pytest.mark.asyncio - async def test_spoiler_without_url(self, bot, ctx): - """Test the response of the `spoiler` command without url.""" - cog = other.OtherCog(bot) - - # Invoke the command. - await cog.spoiler.callback(cog, ctx, url="") - - args, kwargs = ctx.respond.call_args - content = args[0] - - assert isinstance(content, str) - - assert content == "Please provide the spoiler URL." + async def test_spoiler_modal_callback_with_url(self): + modal = SpoilerModal(title="Report Spoiler") + interaction = AsyncMock() + interaction.user.display_name = "TestUser" + modal.children[0].value = "http://example.com/spoiler" + + with patch('aiohttp.ClientSession.post', new_callable=AsyncMock) as mock_post: + mock_post.return_value.__aenter__.return_value.status = 200 + await modal.callback(interaction) + interaction.response.send_message.assert_called_once_with( + "Thank you, the spoiler has been reported.", ephemeral=True + ) + await mock_post.assert_called_once() @pytest.mark.asyncio - async def test_spoiler(self, bot, ctx): - """Test the response of the `spoiler` command.""" - cog = other.OtherCog(bot) - mock_channel = MockTextChannel() - - with patch.object(bot, "get_channel", return_value=mock_channel): - # Invoke the command. - await cog.spoiler.callback(cog, ctx, "https://www.definitely-a-spoiler.com/") - - args, kwargs = ctx.respond.call_args - content = args[0] - ephemeral = kwargs.get("ephemeral") - delete_after = kwargs.get("delete_after") - - # Command should respond with an embed. - assert mock_channel.send.call_count == 1 - assert isinstance(content, str) - assert isinstance(ephemeral, bool) - assert isinstance(delete_after, int) - - assert content == "Thanks for the reporting the spoiler." - assert ephemeral is True - assert delete_after == 15 + async def test_spoiler_modal_callback_with_url(self): + modal = SpoilerModal(title="Report Spoiler") + interaction = AsyncMock() + interaction.user.display_name = "TestUser" + modal.children[0].value = "http://example.com/spoiler" + + with patch('aiohttp.ClientSession.post', new_callable=AsyncMock) as mock_post: + mock_post.return_value.__aenter__.return_value.status = 200 + await modal.callback(interaction) + interaction.response.send_message.assert_called_once_with( + "Thank you, the spoiler has been reported.", ephemeral=True + ) + mock_post.assert_called_once() + @pytest.mark.asyncio + async def test_spoiler_modal_callback_without_url(self): + modal = SpoilerModal(title="Report Spoiler") + interaction = AsyncMock() + interaction.user.display_name = "TestUser" + modal.children[0].value = "" + + await modal.callback(interaction) + interaction.response.send_message.assert_called_once_with( + "Please provide the spoiler URL.", ephemeral=True + ) def test_setup(self, bot): """Test the setup method of the cog.""" # Invoke the command From b898189f393f877bfac6e4a4d604bd323cd1389f Mon Sep 17 00:00:00 2001 From: Tejas <98106526+ToxicBiohazard@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:16:58 +0000 Subject: [PATCH 2/3] Re-Added SLACK_FEEDBACK_WEBHOOK to .test.env --- .test.env | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.test.env b/.test.env index 918654f..2039749 100644 --- a/.test.env +++ b/.test.env @@ -87,4 +87,7 @@ CURRENT_SEASON_ID=1 HTB_API_KEY=CHANGE_ME #Feedback Webhook -JIRA_SPOILER_WEBHOOK="https://automation.atlassian.com/sdfsdfsf" \ No newline at end of file +SLACK_WEBHOOK="https://hook.slack.com/sdfsdfsf" + +#Feedback Webhook +JIRA_SPOILER_WEBHOOK="https://automation.atlassian.com/sdfsdfsf" From f914e1d2a176f28984e639ed6c6352170bc8ad25 Mon Sep 17 00:00:00 2001 From: Tejas <98106526+ToxicBiohazard@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:27:17 +0000 Subject: [PATCH 3/3] Renamed to SLACK_FEEDBACK_WEBHOOK in .test.env --- .test.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.test.env b/.test.env index 2039749..820f8ea 100644 --- a/.test.env +++ b/.test.env @@ -87,7 +87,7 @@ CURRENT_SEASON_ID=1 HTB_API_KEY=CHANGE_ME #Feedback Webhook -SLACK_WEBHOOK="https://hook.slack.com/sdfsdfsf" +SLACK_FEEDBACK_WEBHOOK="https://hook.slack.com/sdfsdfsf" #Feedback Webhook JIRA_SPOILER_WEBHOOK="https://automation.atlassian.com/sdfsdfsf"