Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spoilers are directed to the Jira #125

Merged
merged 3 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .test.env
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,4 @@ CURRENT_SEASON_ID=1
HTB_API_KEY=CHANGE_ME

#Feedback Webhook

SLACK_WEBHOOK="https://hook.slack.com/sdfsdfsf"
JIRA_SPOILER_WEBHOOK="https://automation.atlassian.com/sdfsdfsf"
81 changes: 53 additions & 28 deletions src/cmds/core/other.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,16 +17,16 @@
"""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)

Check warning on line 29 in src/cmds/core/other.py

View check run for this annotation

Codecov / codecov/patch

src/cmds/core/other.py#L29

Added line #L29 was not covered by tests

if interaction.user: # Protects against some weird edge-cases
title = f"{self.children[0].value} - {interaction.user.name}"
Expand Down Expand Up @@ -53,21 +54,53 @@
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()}")

Check warning on line 88 in src/cmds/core/other.py

View check run for this annotation

Codecov / codecov/patch

src/cmds/core/other.py#L87-L88

Added lines #L87 - L88 were not covered by tests
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,
Expand All @@ -86,28 +119,20 @@
"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)

Check warning on line 126 in src/cmds/core/other.py

View check run for this annotation

Codecov / codecov/patch

src/cmds/core/other.py#L125-L126

Added lines #L125 - L126 were not covered by tests

@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))
3 changes: 2 additions & 1 deletion src/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
82 changes: 43 additions & 39 deletions tests/src/cmds/core/test_other.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -12,7 +15,7 @@
@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.
Expand Down Expand Up @@ -80,45 +83,46 @@
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"

Check warning on line 90 in tests/src/cmds/core/test_other.py

View check run for this annotation

Codecov / codecov/patch

tests/src/cmds/core/test_other.py#L87-L90

Added lines #L87 - L90 were not covered by tests

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(

Check warning on line 95 in tests/src/cmds/core/test_other.py

View check run for this annotation

Codecov / codecov/patch

tests/src/cmds/core/test_other.py#L92-L95

Added lines #L92 - L95 were not covered by tests
"Thank you, the spoiler has been reported.", ephemeral=True
)
await mock_post.assert_called_once()

Check warning on line 98 in tests/src/cmds/core/test_other.py

View check run for this annotation

Codecov / codecov/patch

tests/src/cmds/core/test_other.py#L98

Added line #L98 was not covered by tests

@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
Expand Down