diff --git a/bot.py b/bot.py index d62be23..585bdb0 100644 --- a/bot.py +++ b/bot.py @@ -43,7 +43,15 @@ def __init__(self): intents = discord.Intents.default() intents.message_content = True intents.members = True - super().__init__(command_prefix="$", intents=intents, help_command=None) + super().__init__( + command_prefix="$", + intents=intents, + help_command=None, + # глобальный дефолт: чужой текст (вебхуки, /say) не должен пинговать сервер + allowed_mentions=discord.AllowedMentions( + everyone=False, roles=False, users=True + ), + ) self.channel_creators: dict[int, int] = {} self.bot_created_channels: set[int] = set() @@ -166,5 +174,8 @@ async def on_disconnect(self): log.info("Бот отключен") +if not TOKEN: + raise SystemExit("TOKEN не задан: создай .env по образцу .env.example") + bot = CoolBot() bot.run(TOKEN) diff --git a/cogs/anonymous.py b/cogs/anonymous.py index 4d200eb..bb54e04 100644 --- a/cogs/anonymous.py +++ b/cogs/anonymous.py @@ -48,6 +48,8 @@ async def anonsay(self, interaction: discord.Interaction, message: str): content=message[:2000], username=self.bot.user.name, avatar_url=self.bot.user.avatar.url if self.bot.user.avatar else None, + # аноним не должен никого пинговать + allowed_mentions=discord.AllowedMentions.none(), ) await interaction.response.send_message( embed=embeds.ok(description="отправлено", user=interaction.user), diff --git a/cogs/moderation.py b/cogs/moderation.py index f4df280..f23814e 100644 --- a/cogs/moderation.py +++ b/cogs/moderation.py @@ -9,6 +9,8 @@ log = logging.getLogger(__name__) +MAX_PURGE = 200 + class ModerationCog(commands.Cog): def __init__(self, bot: commands.Bot): @@ -21,10 +23,10 @@ def __init__(self, bot: commands.Bot): @app_commands.checks.has_permissions(manage_messages=True) @app_commands.default_permissions(manage_messages=True) async def clear(self, interaction: discord.Interaction, amount: int = 10): - if amount <= 0: + if amount <= 0 or amount > MAX_PURGE: await interaction.response.send_message( embed=embeds.err( - "количество должно быть больше нуля", user=interaction.user + f"количество · от 1 до {MAX_PURGE}", user=interaction.user ), ephemeral=True, ) diff --git a/services/webhook.py b/services/webhook.py index 6b6ba2d..9ba0276 100644 --- a/services/webhook.py +++ b/services/webhook.py @@ -5,6 +5,10 @@ log = logging.getLogger(__name__) +# пересылаемый текст пишут пользователи: пинги людей оставляем, +# @everyone и роли через вебхук пробивать нельзя +SAFE_MENTIONS = discord.AllowedMentions(everyone=False, roles=False, users=True) + class WebhookService: def __init__(self): @@ -34,6 +38,7 @@ def invalidate(self, channel_id: int, name: str): self.cache.pop(f"{channel_id}_{name}", None) async def send(self, channel: discord.TextChannel, name: str, **kwargs): + kwargs.setdefault("allowed_mentions", SAFE_MENTIONS) try: webhook = await self.get_or_create_webhook(channel, name) await webhook.send(**kwargs) diff --git a/tests/test_webhook_mentions.py b/tests/test_webhook_mentions.py new file mode 100644 index 0000000..5840345 --- /dev/null +++ b/tests/test_webhook_mentions.py @@ -0,0 +1,37 @@ +"""Вебхуки не должны пробивать @everyone и роли чужим текстом.""" + +import asyncio +from unittest.mock import AsyncMock + +import discord + +from services.webhook import SAFE_MENTIONS, WebhookService + + +def test_safe_mentions_block_everyone_and_roles(): + assert SAFE_MENTIONS.everyone is False + assert SAFE_MENTIONS.roles is False + assert SAFE_MENTIONS.users is True + + +def test_send_injects_safe_mentions_by_default(): + service = WebhookService() + webhook = AsyncMock() + service.get_or_create_webhook = AsyncMock(return_value=webhook) + + asyncio.run(service.send(AsyncMock(), "Webhook", content="@everyone хех")) + + assert webhook.send.await_args.kwargs["allowed_mentions"] is SAFE_MENTIONS + + +def test_send_keeps_explicit_mentions(): + service = WebhookService() + webhook = AsyncMock() + service.get_or_create_webhook = AsyncMock(return_value=webhook) + none = discord.AllowedMentions.none() + + asyncio.run( + service.send(AsyncMock(), "Webhook", content="x", allowed_mentions=none) + ) + + assert webhook.send.await_args.kwargs["allowed_mentions"] is none