Skip to content

Commit

Permalink
feat: allow resolving queued suggestions with notes
Browse files Browse the repository at this point in the history
BT-22
  • Loading branch information
Skelmis committed May 18, 2024
1 parent 588d68d commit dcec7c0
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 75 deletions.
77 changes: 6 additions & 71 deletions suggestions/cogs/suggestion_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from suggestions import checks, Stats
from suggestions.clunk2 import update_suggestion_message
from suggestions.cooldown_bucket import InteractionBucket
from suggestions.core import SuggestionsQueue
from suggestions.core import SuggestionsQueue, SuggestionsResolutionCore
from suggestions.exceptions import (
SuggestionTooLong,
ErrorHandled,
Expand All @@ -39,6 +39,7 @@ def __init__(self, bot: SuggestionsBot):
self.suggestions_db: Document = self.bot.db.suggestions

self.qs_core: SuggestionsQueue = SuggestionsQueue(bot)
self.resolution_core: SuggestionsResolutionCore = SuggestionsResolutionCore(bot)

@components.button_listener()
async def suggestion_up_vote(
Expand Down Expand Up @@ -371,41 +372,8 @@ async def approve(
suggestion_id: str {{APPROVE_ARG_SUGGESTION_ID}}
response: str {{APPROVE_ARG_RESPONSE}}
"""
guild_config: GuildConfig = await GuildConfig.from_id(
interaction.guild_id, self.state
)
await interaction.response.defer(ephemeral=True)
suggestion: Suggestion = await Suggestion.from_id(
suggestion_id, interaction.guild_id, self.state
)
await suggestion.resolve(
guild_config=guild_config,
state=self.state,
interaction=interaction,
resolution_note=response,
resolution_type=SuggestionState.approved,
bot=self.bot,
)

await interaction.send(
self.bot.get_locale("APPROVE_INNER_MESSAGE", interaction.locale).format(
suggestion_id
),
ephemeral=True,
)
logger.debug(
f"User {interaction.author.id} approved suggestion "
f"{suggestion.suggestion_id} in guild {interaction.guild_id}",
extra_metadata={
"author_id": interaction.author.id,
"guild_id": interaction.guild_id,
"suggestion_id": suggestion.suggestion_id,
},
)
await self.stats.log_stats(
interaction.author.id,
interaction.guild_id,
self.stats.type.APPROVE,
await self.resolution_core.approve(
await InteractionHandler.new_handler(interaction), suggestion_id, response
)

@approve.autocomplete("suggestion_id")
Expand Down Expand Up @@ -434,41 +402,8 @@ async def reject(
suggestion_id: str {{REJECT_ARG_SUGGESTION_ID}}
response: str {{REJECT_ARG_RESPONSE}}
"""
guild_config: GuildConfig = await GuildConfig.from_id(
interaction.guild_id, self.state
)
await interaction.response.defer(ephemeral=True)
suggestion: Suggestion = await Suggestion.from_id(
suggestion_id, interaction.guild_id, self.state
)
await suggestion.resolve(
guild_config=guild_config,
state=self.state,
interaction=interaction,
resolution_note=response,
resolution_type=SuggestionState.rejected,
bot=self.bot,
)

await interaction.send(
self.bot.get_locale("REJECT_INNER_MESSAGE", interaction.locale).format(
suggestion_id
),
ephemeral=True,
)
logger.debug(
f"User {interaction.author} rejected suggestion {suggestion.suggestion_id} "
f"in guild {interaction.guild_id}",
extra_metadata={
"author_id": interaction.author.id,
"guild_id": interaction.guild_id,
"suggestion_id": suggestion.suggestion_id,
},
)
await self.stats.log_stats(
interaction.author.id,
interaction.guild_id,
self.stats.type.REJECT,
await self.resolution_core.reject(
await InteractionHandler.new_handler(interaction), suggestion_id, response
)

@reject.autocomplete("suggestion_id")
Expand Down
1 change: 1 addition & 0 deletions suggestions/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .base import BaseCore
from .suggestions_queue import SuggestionsQueue
from .suggestion_notes import SuggestionsNotesCore
from .suggestion_resolution import SuggestionsResolutionCore
139 changes: 139 additions & 0 deletions suggestions/core/suggestion_resolution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from alaric.comparison import EQ
from logoo import Logger

from suggestions.core import BaseCore, SuggestionsQueue
from suggestions.interaction_handler import InteractionHandler
from suggestions.objects import GuildConfig, Suggestion, QueuedSuggestion
from suggestions.objects.suggestion import SuggestionState

logger = Logger(__name__)


class SuggestionsResolutionCore(BaseCore):
def __init__(self, bot):
super().__init__(bot)
self.qs_core: SuggestionsQueue = SuggestionsQueue(bot)

async def approve(
self, ih: InteractionHandler, suggestion_id: str, response: str | None = None
):
exists = await ih.bot.db.suggestions.count(EQ("_id", suggestion_id))
if exists == 0:
await self.approve_queued_suggestion(ih, suggestion_id, response)
else:
await self.approve_suggestion(ih, suggestion_id, response)

async def approve_queued_suggestion(
self, ih: InteractionHandler, suggestion_id: str, response: str | None = None
):
qs = await QueuedSuggestion.from_id(
suggestion_id, ih.interaction.guild_id, ih.bot.state
)
qs.resolution_note = response
await ih.bot.db.queued_suggestions.update(qs, qs)

await self.qs_core.resolve_queued_suggestion(
ih, queued_suggestion=qs, was_approved=True
)
await ih.send(translation_key="PAGINATION_INNER_QUEUE_ACCEPTED")

async def approve_suggestion(
self, ih: InteractionHandler, suggestion_id: str, response: str | None
):
guild_config: GuildConfig = await GuildConfig.from_id(
ih.interaction.guild_id, ih.bot.state
)
suggestion: Suggestion = await Suggestion.from_id(
suggestion_id, ih.interaction.guild_id, ih.bot.state
)
await suggestion.resolve(
guild_config=guild_config,
state=ih.bot.state,
interaction=ih.interaction,
resolution_note=response,
resolution_type=SuggestionState.approved,
bot=self.bot,
)

await ih.send(
self.bot.get_locale("APPROVE_INNER_MESSAGE", ih.interaction.locale).format(
suggestion_id
),
)
logger.debug(
f"User {ih.interaction.author.id} approved suggestion "
f"{suggestion.suggestion_id} in guild {ih.interaction.guild_id}",
extra_metadata={
"author_id": ih.interaction.author.id,
"guild_id": ih.interaction.guild_id,
"suggestion_id": suggestion.suggestion_id,
},
)
await ih.bot.stats.log_stats(
ih.interaction.author.id,
ih.interaction.guild_id,
ih.bot.stats.type.APPROVE,
)

async def reject(
self, ih: InteractionHandler, suggestion_id: str, response: str | None = None
):
exists = await ih.bot.db.suggestions.count(EQ("_id", suggestion_id))
if exists == 0:
await self.reject_queued_suggestion(ih, suggestion_id, response)
else:
await self.reject_suggestion(ih, suggestion_id, response)

async def reject_queued_suggestion(
self, ih: InteractionHandler, suggestion_id: str, response: str | None = None
):
qs = await QueuedSuggestion.from_id(
suggestion_id, ih.interaction.guild_id, ih.bot.state
)
qs.resolution_note = response
qs.resolved_by = ih.interaction.author.id
await ih.bot.db.queued_suggestions.update(qs, qs)

await self.qs_core.resolve_queued_suggestion(
ih, queued_suggestion=qs, was_approved=False
)
await ih.send(translation_key="PAGINATION_INNER_QUEUE_REJECTED")

async def reject_suggestion(
self, ih: InteractionHandler, suggestion_id: str, response: str | None
):
interaction = ih.interaction
guild_config: GuildConfig = await GuildConfig.from_id(
interaction.guild_id, ih.bot.state
)
suggestion: Suggestion = await Suggestion.from_id(
suggestion_id, interaction.guild_id, ih.bot.state
)
await suggestion.resolve(
guild_config=guild_config,
state=ih.bot.state,
interaction=interaction,
resolution_note=response,
resolution_type=SuggestionState.rejected,
bot=self.bot,
)

await ih.send(
self.bot.get_locale("REJECT_INNER_MESSAGE", interaction.locale).format(
suggestion_id
),
)
logger.debug(
f"User {interaction.author} rejected suggestion {suggestion.suggestion_id} "
f"in guild {interaction.guild_id}",
extra_metadata={
"author_id": interaction.author.id,
"guild_id": interaction.guild_id,
"suggestion_id": suggestion.suggestion_id,
},
)
await ih.bot.stats.log_stats(
interaction.author.id,
interaction.guild_id,
ih.bot.stats.type.REJECT,
)
74 changes: 72 additions & 2 deletions suggestions/objects/queued_suggestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from disnake import Embed
from logoo import Logger

from suggestions.exceptions import UnhandledError, SuggestionNotFound
from suggestions.exceptions import (
UnhandledError,
SuggestionNotFound,
SuggestionSecurityViolation,
)
from suggestions.objects import Suggestion

if TYPE_CHECKING:
Expand Down Expand Up @@ -104,6 +108,53 @@ async def from_message_id(
f"This message does not look like a suggestions message."
)

# A backport for BT-22
if not suggestion._id:
suggestion._id = state.get_new_suggestion_id()
await state.queued_suggestions_db.upsert(suggestion, suggestion)

return suggestion

@classmethod
async def from_id(
cls, suggestion_id: str, guild_id: int, state: State
) -> QueuedSuggestion:
"""Returns a valid QueuedSuggestion instance from an id.
Parameters
----------
suggestion_id: str
The suggestion we want
guild_id: int
The guild its meant to be in.
Secures against cross guild privileged escalation
state: State
Internal state to marshall data
Returns
-------
QueuedSuggestion
The valid suggestion
Raises
------
SuggestionNotFound
No suggestion found with that id
"""
suggestion: Optional[QueuedSuggestion] = await state.queued_suggestions_db.find(
AQ(EQ("_id", suggestion_id))
)
if not suggestion:
raise SuggestionNotFound(
f"No queued suggestion found with the id {suggestion_id} in this guild"
)

if suggestion.guild_id != guild_id:
raise SuggestionSecurityViolation(
sid=suggestion_id,
user_facing_message=f"No queued suggestion found with the id {suggestion_id} in this guild",
)

return suggestion

@classmethod
Expand Down Expand Up @@ -139,13 +190,15 @@ async def new(
Suggestion
A valid suggestion.
"""
_id = state.get_new_suggestion_id()
suggestion: QueuedSuggestion = QueuedSuggestion(
guild_id=guild_id,
suggestion=suggestion,
suggestion_author_id=author_id,
created_at=state.now,
image_url=image_url,
is_anonymous=is_anonymous,
_id=_id,
)
await state.queued_suggestions_db.insert(suggestion)

Expand Down Expand Up @@ -206,14 +259,26 @@ async def as_embed(self, bot: SuggestionsBot) -> Embed:
timestamp=self.created_at,
)
if not self.is_anonymous:
id_section = ""
if self._id and isinstance(self._id, str):
# If longer then 8 it's a database generated id
# and shouldn't be considered for this purpose
id_section = f" ID {self._id}"

embed.set_thumbnail(user.display_avatar)
embed.set_footer(
text=f"Queued suggestion | Submitter ID: {self.suggestion_author_id}"
text=f"Queued suggestion{id_section} | Submitter ID: {self.suggestion_author_id}"
)

if self.image_url:
embed.set_image(self.image_url)

if self.resolution_note and self.resolved_by is not None:
# Means it's been rejected so we should show it
note_desc = f"\n\n**Moderator note**\n{self.resolution_note}"
# TODO Resolve BT-44 and add moderator back
embed.description += note_desc

return embed

async def convert_to_suggestion(self, state: State) -> Suggestion:
Expand All @@ -238,6 +303,11 @@ async def convert_to_suggestion(self, state: State) -> Suggestion:
is_anonymous=self.is_anonymous,
)
self.related_suggestion_id = suggestion.suggestion_id

# Resolution notes default to the suggestion notes
if self.resolution_note:
suggestion.note = self.resolution_note

await state.queued_suggestions_db.update(self, self)
return suggestion

Expand Down
2 changes: 1 addition & 1 deletion suggestions/objects/suggestion.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ async def as_embed(self, bot: SuggestionsBot) -> Embed:

if self.note:
note_desc = f"\n\n**Moderator note**\n{self.note}"
# TODO Resolve BT-44 and add this back
# TODO Resolve BT-44 and add moderator back
embed.description += note_desc

if self.uses_views_for_votes:
Expand Down
Loading

0 comments on commit dcec7c0

Please sign in to comment.