diff --git a/suggestions/bot.py b/suggestions/bot.py index dbd3a6e..58f439a 100644 --- a/suggestions/bot.py +++ b/suggestions/bot.py @@ -33,6 +33,7 @@ ConfiguredChannelNoLongerExists, UnhandledError, QueueImbalance, + BlocklistedUser, ) from suggestions.http_error_parser import try_parse_http_error from suggestions.objects import Error, GuildConfig, UserConfig @@ -45,7 +46,7 @@ class SuggestionsBot(commands.AutoShardedInteractionBot, BotBase): def __init__(self, *args, **kwargs): - self.version: str = "Public Release 3.17" + self.version: str = "Public Release 3.18" self.main_guild_id: int = 601219766258106399 self.legacy_beta_role_id: int = 995588041991274547 self.automated_beta_role_id: int = 998173237282361425 @@ -414,6 +415,17 @@ async def on_slash_command_error( ephemeral=True, ) + elif isinstance(exception, BlocklistedUser): + return await interaction.send( + embed=self.error_embed( + "Blocked Action", + "Administrators from this guild have removed your ability to run this action.", + error_code=ErrorCode.BLOCKLISTED_USER, + error=error, + ), + ephemeral=True, + ) + elif isinstance(exception, ConfiguredChannelNoLongerExists): return await interaction.send( embed=self.error_embed( diff --git a/suggestions/checks.py b/suggestions/checks.py index 33ad60a..944300d 100644 --- a/suggestions/checks.py +++ b/suggestions/checks.py @@ -8,9 +8,9 @@ from disnake.ext import commands from suggestions.exceptions import ( - BetaOnly, MissingSuggestionsChannel, MissingLogsChannel, + BlocklistedUser, ) if TYPE_CHECKING: @@ -62,3 +62,18 @@ async def check(interaction: disnake.Interaction): return True return commands.check(check) # type: ignore + + +def ensure_user_is_not_blocklisted(): + async def check(interaction: disnake.Interaction): + guild_config: Optional[GuildConfig] = await fetch_guild_config(interaction) + + if not bool(guild_config): + return True + + if interaction.author.id in guild_config.blocked_users: + raise BlocklistedUser + + return True + + return commands.check(check) # type: ignore diff --git a/suggestions/codes.py b/suggestions/codes.py index fcc4555..ef7e0d4 100644 --- a/suggestions/codes.py +++ b/suggestions/codes.py @@ -24,6 +24,8 @@ class ErrorCode(IntEnum): MISSING_SEND_PERMISSIONS_IN_SUGGESTION_CHANNEL = 18 MISSING_THREAD_CREATE_PERMISSIONS = 19 QUEUE_IMBALANCE = 20 + MISSING_QUEUE_CHANNEL = 21 + BLOCKLISTED_USER = 22 @classmethod def from_value(cls, value: int) -> ErrorCode: diff --git a/suggestions/cogs/blacklist_cog.py b/suggestions/cogs/blacklist_cog.py new file mode 100644 index 0000000..64f3f19 --- /dev/null +++ b/suggestions/cogs/blacklist_cog.py @@ -0,0 +1,140 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +import disnake +from bot_base import NonExistentEntry +from disnake.ext import commands + +from suggestions.objects import GuildConfig, Suggestion + +if TYPE_CHECKING: + from suggestions import State, SuggestionsBot + +log = logging.getLogger(__name__) + + +class BlacklistCog(commands.Cog): + def __init__(self, bot): + self.bot: SuggestionsBot = bot + self.state: State = self.bot.state + + @commands.slash_command() + async def user(self, interaction: disnake.GuildCommandInteraction): + ... + + @user.sub_command_group() + async def blocklist(self, interaction: disnake.GuildCommandInteraction): + ... + + @blocklist.sub_command() + async def add( + self, + interaction: disnake.GuildCommandInteraction, + suggestion_id: str = commands.Param(), + ): + """ + {{USER_BLOCKLIST_ADD}} + + Parameters + ---------- + suggestion_id: str {{SUGGESTION_ID}} + """ + await interaction.response.defer(ephemeral=True) + suggestion: Suggestion = await Suggestion.from_id( + suggestion_id, interaction.guild_id, self.state + ) + guild_config: GuildConfig = await GuildConfig.from_id( + interaction.guild_id, self.state + ) + if suggestion.suggestion_author_id in guild_config.blocked_users: + return await interaction.send( + "This user is already blocked from creating new suggestions.", + ephemeral=True, + ) + + guild_config.blocked_users.add(suggestion.suggestion_author_id) + await self.bot.db.guild_configs.upsert(guild_config, guild_config) + await interaction.send( + "I have added that user to the blocklist. " + "They will be unable to create suggestions in the future.", + ephemeral=True, + ) + + @blocklist.sub_command() + async def remove( + self, + interaction: disnake.GuildCommandInteraction, + suggestion_id: str = commands.Param(default=None), + user_id: str = commands.Param(default=None), + ): + """ + {{USER_BLOCKLIST_REMOVE}} + + Parameters + ---------- + suggestion_id: str {{SUGGESTION_ID}} + user_id: str {{USER_ID}} + """ + await interaction.response.defer(ephemeral=True) + if suggestion_id and user_id: + return await interaction.send( + "Providing suggestion_id and user_id at the same time is not supported.", + ephemeral=True, + ) + + if suggestion_id: + suggestion: Suggestion = await Suggestion.from_id( + suggestion_id, interaction.guild_id, self.state + ) + user_id = suggestion.suggestion_author_id + + if user_id: + try: + user_id = int(user_id) + except ValueError: + return await interaction.send("User id is not valid.", ephemeral=True) + + guild_config: GuildConfig = await GuildConfig.from_id( + interaction.guild_id, self.state + ) + guild_config.blocked_users.discard(user_id) + await self.bot.db.guild_configs.upsert(guild_config, guild_config) + await interaction.send("I have un-blocklisted that user for you.") + + @add.autocomplete("suggestion_id") + @remove.autocomplete("suggestion_id") + async def get_sid_for( + self, + interaction: disnake.ApplicationCommandInteraction, + user_input: str, + ): + try: + values: list[str] = self.state.autocomplete_cache.get_entry( + interaction.guild_id + ) + except NonExistentEntry: + values: list[str] = await self.state.populate_sid_cache( + interaction.guild_id + ) + else: + if not values: + log.debug( + "Values was found, but empty in guild %s thus populating", + interaction.guild_id, + ) + values: list[str] = await self.state.populate_sid_cache( + interaction.guild_id + ) + + possible_choices = [v for v in values if user_input.lower() in v.lower()] + + if len(possible_choices) > 25: + return [] + + return possible_choices + + +def setup(bot): + bot.add_cog(BlacklistCog(bot)) diff --git a/suggestions/cogs/suggestion_cog.py b/suggestions/cogs/suggestion_cog.py index 3085fe8..5bef9f0 100644 --- a/suggestions/cogs/suggestion_cog.py +++ b/suggestions/cogs/suggestion_cog.py @@ -153,6 +153,7 @@ async def suggestion_down_vote( dm_permission=False, ) @cooldowns.cooldown(1, 3, bucket=InteractionBucket.author) + @checks.ensure_user_is_not_blocklisted() @checks.ensure_guild_has_suggestions_channel() async def suggest( self, diff --git a/suggestions/exceptions.py b/suggestions/exceptions.py index c343c70..1e78006 100644 --- a/suggestions/exceptions.py +++ b/suggestions/exceptions.py @@ -45,3 +45,7 @@ class UnhandledError(Exception): class QueueImbalance(disnake.DiscordException): """This queued suggestion has already been dealt with in another queue.""" + + +class BlocklistedUser(CheckFailure): + """This user is blocked from taking this action in this guild.""" diff --git a/suggestions/locales/en_GB.json b/suggestions/locales/en_GB.json index d78d8e3..4d3cbc5 100644 --- a/suggestions/locales/en_GB.json +++ b/suggestions/locales/en_GB.json @@ -113,5 +113,13 @@ "CONFIG_GET_INNER_IMAGES_IN_SUGGESTIONS_MESSAGE": "This guild {} have images in suggestions.", "CONFIG_SUGGESTIONS_IMAGES_ENABLE_INNER_MESSAGE": "All new suggestions can include images.", "CONFIG_SUGGESTIONS_IMAGES_DISABLE_INNER_MESSAGE": "All new suggestions cannot include images.", - "VIEW_VOTERS_INNER_EMBED_TITLE": "Viewing voters" + "VIEW_VOTERS_INNER_EMBED_TITLE": "Viewing voters", + "USER_BLOCKLIST_ADD_NAME": "add", + "USER_BLOCKLIST_ADD_DESCRIPTION": "Remove a users ability to create suggestions.", + "USER_BLOCKLIST_REMOVE_NAME": "remove", + "USER_BLOCKLIST_REMOVE_DESCRIPTION": "Re-add a users ability to create suggestions.", + "SUGGESTION_ID_NAME": "suggestion_id", + "SUGGESTION_ID_DESCRIPTION": "The suggestions ID you wish to reference.", + "USER_ID_NAME": "user_id", + "USER_ID_DESCRIPTION": "The users discord id." } diff --git a/suggestions/locales/en_US.json b/suggestions/locales/en_US.json new file mode 100644 index 0000000..4d3cbc5 --- /dev/null +++ b/suggestions/locales/en_US.json @@ -0,0 +1,125 @@ +{ + "SUGGEST_NAME": "suggest", + "SUGGEST_DESCRIPTION": "Create a new suggestion.", + "SUGGEST_ARG_SUGGESTION_NAME": "suggestion", + "SUGGEST_ARG_SUGGESTION_DESCRIPTION": "Your suggestion.", + "SUGGEST_ARG_IMAGE_NAME": "image", + "SUGGEST_ARG_IMAGE_DESCRIPTION": "An image to add to your suggestion. Images currently expire after a couple weeks.", + "SUGGEST_ARG_ANONYMOUSLY_NAME": "anonymously", + "SUGGEST_ARG_ANONYMOUSLY_DESCRIPTION": "Submit your suggestion anonymously.", + "APPROVE_NAME": "approve", + "APPROVE_DESCRIPTION": "Approve a suggestion", + "APPROVE_ARG_SUGGESTION_ID_NAME": "suggestion_id", + "APPROVE_ARG_SUGGESTION_ID_DESCRIPTION": "The sID you wish to approve", + "APPROVE_ARG_RESPONSE_NAME": "response", + "APPROVE_ARG_RESPONSE_DESCRIPTION": "An optional response to add to the suggestion", + "REJECT_NAME": "reject", + "REJECT_DESCRIPTION": "Reject a suggestion", + "REJECT_ARG_SUGGESTION_ID_NAME": "suggestion_id", + "REJECT_ARG_SUGGESTION_ID_DESCRIPTION": "The sID you wish to reject", + "REJECT_ARG_RESPONSE_NAME": "response", + "REJECT_ARG_RESPONSE_DESCRIPTION": "An optional response to add to the suggestion", + "CLEAR_NAME": "clear", + "CLEAR_DESCRIPTION": "Remove a suggestion and any associated messages.", + "CLEAR_ARG_SUGGESTION_ID_NAME": "suggestion_id", + "CLEAR_ARG_SUGGESTION_ID_DESCRIPTION": "The sID you wish to reject", + "CLEAR_ARG_RESPONSE_NAME": "response", + "CLEAR_ARG_RESPONSE_DESCRIPTION": "An optional response to add to the suggestion", + "STATS_NAME": "stats", + "STATS_DESCRIPTION": "Get bot stats!", + "INFO_NAME": "info", + "INFO_DESCRIPTION": "View bot information.", + "INFO_ARG_SUPPORT_NAME": "support", + "INFO_ARG_SUPPORT_DESCRIPTION": "Set this to receive info relevant to receiving official support.", + "PING_NAME": "ping", + "PING_DESCRIPTION": "Pong!", + "SUGGESTION_UP_VOTE_INNER_NO_MORE_CASTING": "You can no longer cast votes on this suggestion.", + "SUGGESTION_UP_VOTE_INNER_ALREADY_VOTED": "You have already up voted this suggestion.", + "SUGGESTION_UP_VOTE_INNER_MODIFIED_VOTE": "I have changed your vote from a down vote to an up vote for this suggestion.\nThe suggestion will be updated shortly.", + "SUGGESTION_UP_VOTE_INNER_REGISTERED_VOTE": "Thanks!\nI have registered your up vote.", + "SUGGESTION_DOWN_VOTE_INNER_NO_MORE_CASTING": "You can no longer cast votes on this suggestion.", + "SUGGESTION_DOWN_VOTE_INNER_ALREADY_VOTED": "You have already down voted this suggestion.", + "SUGGESTION_DOWN_VOTE_INNER_MODIFIED_VOTE": "I have changed your vote from an up vote to a down vote for this suggestion.\nThe suggestion will be updated shortly.", + "SUGGESTION_DOWN_VOTE_INNER_REGISTERED_VOTE": "Thanks!\nI have registered your down vote.", + "VIEW_VOTERS_INNER_TITLE_PREFIX": "Voters", + "VIEW_UP_VOTERS_INNER_TITLE_PREFIX": "Up voters", + "VIEW_DOWN_VOTERS_INNER_TITLE_PREFIX": "Down voters", + "DISPLAY_DATA_INNER_OLD_SUGGESTION_TYPE": "Suggestions using reactions are not supported by this command.", + "DISPLAY_DATA_INNER_NO_VOTERS": "There are no voters to show you for this.", + "VOTER_PAGINATOR_INNER_EMBED_TITLE": "{} for suggestion `{}`", + "VOTER_PAGINATOR_INNER_EMBED_FOOTER": "Page {} of {}", + "APPROVE_INNER_MESSAGE": "You have approved **{}**", + "REJECT_INNER_MESSAGE": "You have rejected **{}**", + "CLEAR_INNER_MESSAGE": "I have cleared `{}` for you.", + "SUGGEST_INNER_SUGGESTION_SENT": "Hey, {}. Your suggestion has been sent to {} to be voted on!\n\nPlease wait until it gets approved or rejected by a staff member.\n\nYour suggestion ID (sID) for reference is **{}**.", + "SUGGEST_INNER_SUGGESTION_SENT_FOOTER": "Guild ID: {} | sID: {}", + "SUGGEST_INNER_THANKS": "Thanks for your suggestion!", + "SUGGEST_INNER_SENT_TO_QUEUE": "Your suggestion has been sent to the queue for processing.", + "SUGGEST_INNER_NO_ANONYMOUS_SUGGESTIONS": "Your guild does not allow anonymous suggestions.", + "SUGGEST_INNER_NO_IMAGES_IN_SUGGESTIONS": "Your guild does not allow images in suggestions.", + "CONFIG_ANONYMOUS_ENABLE_INNER_SUCCESS": "I have enabled anonymous suggestions for this guild.", + "CONFIG_ANONYMOUS_DISABLE_INNER_SUCCESS": "I have disabled anonymous suggestions for this guild.", + "SUGGESTION_OBJECT_LOCK_THREAD": "Locking this thread as the suggestion has reached a resolution.", + "CONFIG_CHANNEL_INNER_MESSAGE": "I have set this guilds suggestion channel to {}", + "CONFIG_LOGS_INNER_MESSAGE": "I have set this guilds log channel to {}", + "CONFIG_GET_INNER_BASE_EMBED_DESCRIPTION": "Configuration for {}\n", + "CONFIG_GET_INNER_PARTIAL_LOG_CHANNEL_SET": "Log channel: <#{}>", + "CONFIG_GET_INNER_PARTIAL_LOG_CHANNEL_NOT_SET": "Not set", + "CONFIG_GET_INNER_PARTIAL_SUGGESTION_CHANNEL_SET": "Suggestion channel: <#{}>", + "CONFIG_GET_INNER_PARTIAL_SUGGESTION_CHANNEL_NOT_SET": "Not set", + "CONFIG_GET_INNER_PARTIAL_DM_RESPONSES_SET": "will", + "CONFIG_GET_INNER_PARTIAL_DM_RESPONSES_NOT_SET": "will not", + "CONFIG_GET_INNER_PARTIAL_DM_RESPONSES_MESSAGE": "Dm responses: I {} DM users on actions such as suggest", + "CONFIG_GET_INNER_PARTIAL_THREADS_SET": "will", + "CONFIG_GET_INNER_PARTIAL_THREADS_NOT_SET": "will not", + "CONFIG_GET_INNER_PARTIAL_THREADS_MESSAGE": "I {} create threads for new suggestions", + "CONFIG_GET_INNER_KEEP_LOGS_SET": "Suggestion logs will be kept in your suggestions channel.", + "CONFIG_GET_INNER_KEEP_LOGS_NOT_SET": "Suggestion logs will be kept in your logs channel.", + "CONFIG_GET_INNER_ANONYMOUS_SUGGESTIONS_SET": "can", + "CONFIG_GET_INNER_ANONYMOUS_SUGGESTIONS_NOT_SET": "cannot", + "CONFIG_GET_INNER_ANONYMOUS_SUGGESTIONS_MESSAGE": "This guild {} have anonymous suggestions.", + "CONFIG_GET_INNER_AUTO_ARCHIVE_THREADS_SET": "will", + "CONFIG_GET_INNER_AUTO_ARCHIVE_THREADS_NOT_SET": "will not", + "CONFIG_GET_INNER_AUTO_ARCHIVE_THREADS_MESSAGE": "I {} automatically archive threads created for suggestions.", + "CONFIG_DM_ENABLE_INNER_MESSAGE": "I have enabled DM messages for this guild.", + "CONFIG_DM_DISABLE_INNER_MESSAGE": "I have disabled DM messages for this guild.", + "CONFIG_THREAD_ENABLE_INNER_MESSAGE": "I have enabled threads on new suggestions for this guild.", + "CONFIG_THREAD_DISABLE_INNER_MESSAGE": "I have disabled thread creation on new suggestions for this guild.", + "CONFIG_KEEPLOGS_ENABLE_INNER_MESSAGE": "Suggestions will now stay in your suggestions channel instead of going to logs.", + "CONFIG_KEEPLOGS_DISABLE_INNER_MESSAGE": "Suggestions will now be moved to your logs channel when finished.", + "CONFIG_AUTO_ARCHIVE_THREADS_ENABLE_INNER_MESSAGE": "Automatically created threads for suggestions will now be archived upon suggestion resolution.", + "CONFIG_AUTO_ARCHIVE_THREADS_DISABLE_INNER_MESSAGE": "Automatically created threads for suggestions will no longer be archived upon suggestion resolution.", + "CONFIG_SUGGESTIONS_QUEUE_ENABLE_INNER_MESSAGE": "All new suggestions will be sent to your suggestions queue.", + "CONFIG_SUGGESTIONS_QUEUE_DISABLE_INNER_MESSAGE": "All new suggestions will be sent directly to your suggestions channel.", + "CONFIG_ANONYMOUS_RESOLUTION_ENABLE_INNER_MESSAGE": "All further suggestions will not show the moderator who resolved them.", + "CONFIG_ANONYMOUS_RESOLUTION_DISABLE_INNER_MESSAGE": "All further suggestions will show the moderator who resolved them.", + "CONFIG_GET_INNER_SUGGESTIONS_QUEUE_SET": "In use.", + "CONFIG_GET_INNER_SUGGESTIONS_QUEUE_NOT_SET": "Not in use.", + "CONFIG_GET_INNER_SUGGESTIONS_QUEUE_MESSAGE": "Suggestions queue is $TEXT", + "CONFIG_GET_INNER_ANONYMOUS_RESOLUTION_NOT_SET": "Suggesters are shown who resolved their suggestions.", + "CONFIG_GET_INNER_ANONYMOUS_RESOLUTION_SET": "Suggesters are not shown who resolved their suggestions.", + "PAGINATION_INNER_SESSION_EXPIRED": "This pagination session has expired, please start a new one with `/queue view`", + "PAGINATION_INNER_NEXT_ITEM": "Viewing next item in queue.", + "PAGINATION_INNER_PREVIOUS_ITEM": "Viewing previous item in queue.", + "PAGINATION_INNER_QUEUE_EXPIRED": "This queue has expired.", + "PAGINATION_INNER_QUEUE_CANCELLED": "I have cancelled this queue for you.", + "PAGINATION_INNER_QUEUE_ACCEPTED": "I have accepted that suggestion from the queue.", + "PAGINATION_INNER_QUEUE_REJECTED": "I have removed that suggestion from the queue.", + "QUEUE_VIEW_INNER_NOTHING_QUEUED": "Your guild has no suggestions in the queue.", + "QUEUE_VIEW_INNER_PRIOR_QUEUE": "These suggestions were queued before your guild disabled the suggestions queue.", + "QUEUE_INNER_USER_REJECTED": "Your queued suggestion was rejected.", + "CONFIG_GET_INNER_IMAGES_IN_SUGGESTIONS_SET": "can", + "CONFIG_GET_INNER_IMAGES_IN_SUGGESTIONS_NOT_SET": "cannot", + "CONFIG_GET_INNER_IMAGES_IN_SUGGESTIONS_MESSAGE": "This guild {} have images in suggestions.", + "CONFIG_SUGGESTIONS_IMAGES_ENABLE_INNER_MESSAGE": "All new suggestions can include images.", + "CONFIG_SUGGESTIONS_IMAGES_DISABLE_INNER_MESSAGE": "All new suggestions cannot include images.", + "VIEW_VOTERS_INNER_EMBED_TITLE": "Viewing voters", + "USER_BLOCKLIST_ADD_NAME": "add", + "USER_BLOCKLIST_ADD_DESCRIPTION": "Remove a users ability to create suggestions.", + "USER_BLOCKLIST_REMOVE_NAME": "remove", + "USER_BLOCKLIST_REMOVE_DESCRIPTION": "Re-add a users ability to create suggestions.", + "SUGGESTION_ID_NAME": "suggestion_id", + "SUGGESTION_ID_DESCRIPTION": "The suggestions ID you wish to reference.", + "USER_ID_NAME": "user_id", + "USER_ID_DESCRIPTION": "The users discord id." +} diff --git a/suggestions/locales/pt_BR.json b/suggestions/locales/pt_BR.json new file mode 100644 index 0000000..d058a04 --- /dev/null +++ b/suggestions/locales/pt_BR.json @@ -0,0 +1,116 @@ +{ + "SUGGEST_NAME": "sugerir", + "SUGGESTION_DESCRIPTION": "Crie uma nova sugestão.", + "SUGGESTION_ARG_SUGGESTION_DESCRIPTION": "Sua sugestão.", + "SUGGEST_ARG_IMAGE_NAME": "imagem", + "SUGGESTION_ARG_IMAGE_DESCRIPTION": "Uma imagem para adicionar a sua sugestão. Atualmente, as imagens expiram após algumas semanas.", + "SUGGEST_ARG_ANONYMOUSLY_NAME": "anonimamente", + "SUGGEST_ARG_ANONYMOUSLY_DESCRIPTION": "Envie sua sugestão anonimamente.", + "APPROVE_NAME": "aprovar", + "APPROVE_DESCRIPTION": "Aprove uma sugestão", + "APPROVE_ARG_SUGGESTION_ID_NAME": "id_da_sugestão", + "APPROVE_ARG_SUGGESTION_ID_DESCRIPTION": "O sID que você deseja aprovar", + "APPROVE_ARG_RESPONSE_NAME": "resposta", + "APPROVE_ARG_RESPONSE_DESCRIPTION": "Uma resposta opcional para adicionar a sua sugestão.", + "REJECT_NAME": "rejeitar", + "REJECT_DESCRIPTION": "Rejeite uma sugestão", + "REJECT_ARG_SUGGESTION_ID_NAME": "id_da_sugestão", + "REJECT_ARG_SUGGESTION_ID_DESCRIPTION": "O sID que você deseja rejeitar", + "REJECT_ARG_RESPONSE_NAME": "resposta", + "REJECT_ARG_RESPONSE_DESCRIPTION": "Uma resposta opcional para adicionar a sua sugestão.", + "CLEAR_NAME": "remover", + "CLEAR_DESCRIPTION": "Remova uma sugestão e quaisquer mensagens associadas a ela.", + "CLEAR_ARG_SUGGESTION_ID_NAME": "id_da_sugestão", + "CLEAR_ARG_SUGGESTION_ID_DESCRIPTION": "O sID que você deseja remover", + "CLEAR_ARG_RESPONSE_NAME": "resposta", + "CLEAR_ARG_RESPONSE_DESCRIPTION": "Uma resposta opcional sobre por que se removeu esta sugestão.", + "STATS_NAME": "estatísticas", + "STATS_DESCRIPTION": "Obtenha estatísticas do bot!", + "INFO_NAME": "info", + "INFO_DESCRIPTION": "Veja informações do bot.", + "INFO_ARG_SUPPORT_NAME": "suporte", + "INFO_ARG_SUPPORT_DESCRIPTION": "Configure isto para receber informação relevante a receber suporte oficial.", + "PING_NAME": "pingue", + "PING_DESCRIPTION": "Pongue!", + "SUGGESTION_UP_VOTE_INNER_NO_MORE_CASTING": "Você não pode mais dar votos a esta sugestão.", + "SUGGESTION_UP_VOTE_INNER_ALREADY_VOTED": "Você já votou positivamente nesta sugestão.", + "SUGGESTION_UP_VOTE_INNER_MODIFIED_VOTE": "Mudei seu voto de um negativo para um positivo.\nA sugestão será atualizada em breve.", + "SUGGESTION_UP_VOTE_INNER_REGISTERED_VOTE": "Obrigado!\nRegistrei seu voto positivo.", + "SUGGESTION_DOWN_VOTE_INNER_NO_MORE_CASTING": "Você não pode mais dar votos a esta sugestão.", + "SUGGESTION_DOWN_VOTE_INNER_ALREADY_VOTED": "Você já votou negativamente nesta sugestão.", + "SUGGESTION_DOWN_VOTE_INNER_MODIFIED_VOTE": "Mudei seu voto de um positivo para um negativo.\nA sugestão será atualizada em breve.", + "SUGGESTION_DOWN_VOTE_INNER_REGISTERED_VOTE": "Obrigado!\nRegistrei seu voto negativo.", + "VIEW_VOTERS_INNER_TITLE_PREFIX": "Votantes", + "VIEW_UP_VOTERS_INNER_TITLE_PREFIX": "Votantes positivos", + "VIEW_DOWN_VOTERS_INNER_TITLE_PREFIX": "Votantes negativos", + "DISPLAY_DATA_INNER_OLD_SUGGESTION_TYPE": "Sugestões usando reações não são compatíveis com este comando.", + "DISPLAY_DATA_INNER_NO_VOTERS": "Não há votantes desta sugestão para lhe mostrar.", + "VOTER_PAGINATOR_INNER_EMBED_TITLE": "{} para a sugestão `{}`", + "VOTER_PAGINATOR_INNER_EMBED_FOOTER": "Página {} de {}", + "APPROVE_INNER_MESSAGE": "Você aprovou **{}**", + "REJECT_INNER_MESSAGE": "Você rejeitou **{}**", + "CLEAR_INNER_MESSAGE": "Removi `{}` para você.", + "SUGGEST_INNER_SUGGESTION_SENT": "Ei, {}. Sua sugestão foi enviada para {} para ser votada!\n\nPor favor, aguarde até ela ser aprovada ou rejeitada por um membro da staff.\n\nPara referência, seu ID de sugestão (sID) é {}.", + "SUGGEST_INNER_SUGGESTION_SENT_FOOTER": "ID do servidor: {} | sID: {}", + "SUGGEST_INNER_THANKS": "Obrigado por sua sugestão!", + "SUGGEST_INNER_SENT_TO_QUEUE": "Enviou-se sua sugestão à fila para processamento.", + "SUGGEST_INNER_NO_ANONYMOUS_SUGGESTIONS": "Seu servidor não permite sugestões anônimas.", + "SUGGEST_INNER_NO_IMAGES_IN_SUGGESTIONS": "Seu servidor não permite imagens em sugestões.", + "CONFIG_ANONYMOUS_ENABLE_INNER_SUCCESS": "Habilitei sugestões anônimas para este servidor.", + "CONFIG_ANONYMOUS_DISABLE_INNER_SUCCESS": "Desabilitei sugestões anônimas para este servidor.", + "SUGGESTION_OBJECT_LOCK_THREAD": "Trancando este tópico já que a sugestão chegou a uma resolução.", + "CONFIG_CHANNEL_INNER_MESSAGE": "Configurei o canal de sugestões deste servidor em {}", + "CONFIG_LOGS_INNER_MESSAGE": "Configurei o canal de sugestões deste servidor em {}", + "CONFIG_GET_INNER_BASE_EMBED_DESCRIPTION": "Configuração para {}\n", + "CONFIG_GET_INNER_PARTIAL_LOG_CHANNEL_SET": "Canal de registros: <#{}>", + "CONFIG_GET_INNER_PARTIAL_LOG_CHANNEL_NOT_SET": "Não configurado", + "CONFIG_GET_INNER_PARTIAL_SUGGESTION_CHANNEL_SET": "Canal de sugestões: <#{}>", + "CONFIG_GET_INNER_PARTIAL_SUGGESTION_CHANNEL_NOT_SET": "Não configurado", + "CONFIG_GET_INNER_PARTIAL_DM_RESPONSES_SET": "Vou", + "CONFIG_GET_INNER_PARTIAL_DM_RESPONSES_NOT_SET": "Não vou", + "CONFIG_GET_INNER_PARTIAL_DM_RESPONSES_MESSAGE": "Respostas em mensagem direta: {} enviar a usuários mensagens diretas sobre ações, tais como sugestões.", + "CONFIG_GET_INNER_PARTIAL_THREADS_SET": "Vou", + "CONFIG_GET_INNER_PARTIAL_THREADS_NOT_SET": "Não vou", + "CONFIG_GET_INNER_PARTIAL_THREADS_MESSAGE": "{} criar tópicos para novas sugestões", + "CONFIG_GET_INNER_KEEP_LOGS_SET": "Registros de sugestões serão mantidos no seu canal de sugestões.", + "CONFIG_GET_INNER_KEEP_LOGS_NOT_SET": "Registros de sugestões serão mantidos no seu canal de registros.", + "CONFIG_GET_INNER_ANONYMOUS_SUGGESTIONS_SET": "pode", + "CONFIG_GET_INNER_ANONYMOUS_SUGGESTIONS_NOT_SET": "não pode", + "CONFIG_GET_INNER_ANONYMOUS_SUGGESTIONS_MESSAGE": "Este servidor {} ter sugestões anônimas.", + "CONFIG_GET_INNER_AUTO_ARCHIVE_THREADS_SET": "Vou", + "CONFIG_GET_INNER_AUTO_ARCHIVE_THREADS_NOT_SET": "Não vou", + "CONFIG_GET_INNER_AUTO_ARCHIVE_THREADS_MESSAGE": "{} trancar automaticamente tópicos criados para sugestões.", + "CONFIG_DM_ENABLE_INNER_MESSAGE": "Habilitei mensagens diretas para este servidor.", + "CONFIG_DM_DISABLE_INNER_MESSAGE": "Desabilitei mensagens diretas para este servidor.", + "CONFIG_THREAD_ENABLE_INNER_MESSAGE": "Habilitei tópicos em novas sugestões para este servidor.", + "CONFIG_THREAD_DISABLE_INNER_MESSAGE": "Desabilitei tópicos em novas sugestões para este servidor.", + "CONFIG_KEEPLOGS_ENABLE_INNER_MESSAGE": "Registros de sugestões agora serão mantidos no seu canal de sugestões.", + "CONFIG_KEEPLOGS_DISABLE_INNER_MESSAGE": "Registros de sugestões agora serão movidas para seu canal de registros quando finalizadas", + "CONFIG_AUTO_ARCHIVE_THREADS_ENABLE_INNER_MESSAGE": "Tópicos criados automaticamente para sugestões agora serão arquivados na resolução da sugestão.", + "CONFIG_AUTO_ARCHIVE_THREADS_DISABLE_INNER_MESSAGE": "Tópicos criados automaticamente para sugestões não serão mais arquivados na resolução da sugestão.", + "CONFIG_SUGGESTIONS_QUEUE_ENABLE_INNER_MESSAGE": "Todas as novas sugestões serão enviadas a sua fila de sugestões.", + "CONFIG_SUGGESTIONS_QUEUE_DISABLE_INNER_MESSAGE": "Todas as novas sugestões serão enviadas diretamente a seu canal de sugestões.", + "CONFIG_ANONYMOUS_RESOLUTION_ENABLE_INNER_MESSAGE": "Todas as outras sugestões não vão mostrar o moderador que as resolveu.", + "CONFIG_ANONYMOUS_RESOLUTION_DISABLE_INNER_MESSAGE": "Todas as outras sugestões vão mostrar o moderador que as resolveu.", + "CONFIG_GET_INNER_SUGGESTIONS_QUEUE_SET": "ativada.", + "CONFIG_GET_INNER_SUGGESTIONS_QUEUE_NOT_SET": "desativada.", + "CONFIG_GET_INNER_SUGGESTIONS_QUEUE_MESSAGE": "A fila de sugestões está $TEXT", + "CONFIG_GET_INNER_ANONYMOUS_RESOLUTION_NOT_SET": "Sugestores vão ver quem resolveram as suas sugestões.", + "CONFIG_GET_INNER_ANONYMOUS_RESOLUTION_SET": "Sugestores não vão ver quem resolveram as suas sugestões.", + "PAGINATION_INNER_SESSION_EXPIRED": "Esta sessão de paginação expirou; por favor, inicie uma nova com `/queue view`", + "PAGINATION_INNER_NEXT_ITEM": "Vendo o próximo item na fila...", + "PAGINATION_INNER_PREVIOUS_ITEM": "Vendo o item anterior na fila...", + "PAGINATION_INNER_QUEUE_EXPIRED": "Esta fila expirou.", + "PAGINATION_INNER_QUEUE_CANCELLED": "Anulei esta fila de espera para você.", + "PAGINATION_INNER_QUEUE_ACCEPTED": "Aceitei essa sugestão da fila.", + "PAGINATION_INNER_QUEUE_REJECTED": "Removi essa sugestão da fila.", + "QUEUE_VIEW_INNER_NOTHING_QUEUED": "Seu servidor não tem sugestões na fila.", + "QUEUE_VIEW_INNER_PRIOR_QUEUE": "Colocaram-se estas sugestões na fila antes que seu servidor desativasse a fila de sugestões.", + "QUEUE_INNER_USER_REJECTED": "Sua sugestão que estava na fila foi rejeitada.", + "CONFIG_GET_INNER_IMAGES_IN_SUGGESTIONS_SET": "pode", + "CONFIG_GET_INNER_IMAGES_IN_SUGGESTIONS_NOT_SET": "não pode", + "CONFIG_GET_INNER_IMAGES_IN_SUGGESTIONS_MESSAGE": "Este servidor {} ter imagens em sugestões.", + "CONFIG_SUGGESTIONS_IMAGES_ENABLE_INNER_MESSAGE": "Todas as novas sugestões podem incluir imagens.", + "CONFIG_SUGGESTIONS_IMAGES_DISABLE_INNER_MESSAGE": "Todas as novas sugestões não podem incluir imagens.", + "VIEW_VOTERS_INNER_EMBED_TITLE": "Vendo votantes..." +} diff --git a/suggestions/main.py b/suggestions/main.py index f0ba57a..f1d6074 100644 --- a/suggestions/main.py +++ b/suggestions/main.py @@ -11,6 +11,7 @@ import cooldowns import disnake +from disnake import Locale from disnake.ext import commands from bot_base.paginators.disnake_paginator import DisnakePaginator @@ -154,6 +155,18 @@ async def info( ) embed.add_field("Version", bot.version) embed.set_footer(text=f"© {year} Anthony Collier") + + translations = { + Locale.pt_BR: { + "author": 651386805043593237, + "language": "Portuguese, Brazilian", + "username": "Davi", + } + } + if interaction.locale in translations: + data = translations[interaction.locale] + embed.description += f"\n\n{data['language']} translations by {data['username']}(`{data['author']}`)" + await interaction.send(embed=embed) @bot.slash_command() diff --git a/suggestions/objects/guild_config.py b/suggestions/objects/guild_config.py index aefdb0e..389386d 100644 --- a/suggestions/objects/guild_config.py +++ b/suggestions/objects/guild_config.py @@ -27,6 +27,7 @@ def __init__( uses_suggestion_queue: bool = False, can_have_images_in_suggestions: bool = True, anonymous_resolutions: bool = False, + blocked_users: Optional[list[int]] = None, **kwargs, ): self._id: int = _id @@ -41,6 +42,10 @@ def __init__( self.can_have_anonymous_suggestions: bool = can_have_anonymous_suggestions self.can_have_images_in_suggestions: bool = can_have_images_in_suggestions + if blocked_users is None: + blocked_users = set() + self.blocked_users: set[int] = set(blocked_users) + @property def guild_id(self) -> int: return self._id @@ -82,6 +87,7 @@ def as_dict(self) -> Dict: return { "_id": self.guild_id, "keep_logs": self.keep_logs, + "blocked_users": list(self.blocked_users), "log_channel_id": self.log_channel_id, "auto_archive_threads": self.auto_archive_threads, "dm_messages_disabled": self.dm_messages_disabled, diff --git a/tests/test_bot.py b/tests/test_bot.py index 44a1fc4..9dc4f01 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -17,6 +17,7 @@ async def test_cogs_loaded(causar: Causar): "ViewVotersCog", "SuggestionsMessageCommands", "SuggestionsQueueCog", + "BlacklistCog", ] assert len(bot.cogs) == len(cog_names) for cog_name in cog_names: