From 2c6629f7f92df059d92fbb42ed70c6118f8b7714 Mon Sep 17 00:00:00 2001 From: valer Date: Fri, 6 Mar 2026 14:25:57 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D0=BC=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=B8?= =?UTF-8?q?=D0=BA=D0=BD=D0=B5=D0=B9=D0=BC=D0=B0=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D1=83=D1=85=D0=BE=D0=B4=D0=B5=20=D0=B2=20AFK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/bot.py | 1 + bot/cogs/events.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++ bot/cogs/slash.py | 2 ++ configs/config.py | 8 ++++++ 4 files changed, 74 insertions(+) diff --git a/bot/bot.py b/bot/bot.py index 80c0433..4c819d4 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -29,6 +29,7 @@ class Bot(commands.Bot): intents.guilds = True intents.message_content = True intents.members = True + intents.presences = True command_prefix: str = prefix or getattr(settings, "PREFIX", "!") diff --git a/bot/cogs/events.py b/bot/cogs/events.py index 0da5364..d34c379 100644 --- a/bot/cogs/events.py +++ b/bot/cogs/events.py @@ -20,6 +20,69 @@ class Events(Cog): self.check_reminders.start() @Cog.listener() + async def on_presence_update(self, before: discord.Member, after: discord.Member) -> None: + """ + Следим за конкретным пользователем и меняем ник на '[AFK] Ник' при уходе в Idle, + а при возвращении восстанавливаем оригинальный ник. + """ + afk_nickname = getattr(settings, "AFK_NICKNAME", "AFK") + # если статус не изменился — выходим + if before.status == after.status: + return + # словарь для хранения оригинального ника + if not hasattr(self, 'original_nick'): + self.original_nick = {} + + # пользователь ушёл в AFK/Idle + if after.status == discord.Status.idle: + # если уже AFK, ничего не делаем + if after.nick and after.nick.startswith(afk_nickname): + return + + # сохраняем оригинальный ник + self.original_nick[after.id] = after.nick or after.name + + try: + await after.edit(nick=afk_nickname) + logger.info( + text=f"{after} ушёл в AFK, ник изменён", + log_type="PRESENCE", + user=str(after), + ) + except discord.Forbidden: + logger.warning( + text=f"Нет прав изменить ник {after}", + log_type="PRESENCE", + ) + except discord.HTTPException as e: + logger.error( + text=f"Ошибка смены ника {after}: {e!r}", + log_type="PRESENCE", + ) + + # пользователь вернулся из AFK + elif before.status == discord.Status.idle and after.status != discord.Status.idle: + original = self.original_nick.get(after.id) + if original: + try: + await after.edit(nick=original) + logger.info( + text=f"{after} вернулся из AFK, ник восстановлен", + log_type="PRESENCE", + user=str(after), + ) + del self.original_nick[after.id] # убираем из словаря + except discord.Forbidden: + logger.warning( + text=f"Нет прав вернуть ник {after}", + log_type="PRESENCE", + ) + except discord.HTTPException as e: + logger.error( + text=f"Ошибка возврата ника {after}: {e!r}", + log_type="PRESENCE", + ) + @Cog.listener() async def on_ready(self) -> None: """ Событие запуска бота. diff --git a/bot/cogs/slash.py b/bot/cogs/slash.py index d5a13cd..00c8457 100644 --- a/bot/cogs/slash.py +++ b/bot/cogs/slash.py @@ -41,5 +41,7 @@ class Slash(commands.Cog): ) + + async def setup(bot: commands.Bot) -> None: await bot.add_cog(Slash(bot)) diff --git a/configs/config.py b/configs/config.py index b3d47d6..4a54625 100644 --- a/configs/config.py +++ b/configs/config.py @@ -20,6 +20,7 @@ class _Settings(BaseSettings): WELCOME_CHANNEL_ID: int = 0 ADMIN_ROLE_NAME: str = "Администратор" + AFK_NICKNAME:str ="AFK" WARNINGS_FILE: Path = Path("data/warnings.json") REMINDERS_FILE: Path = Path("data/reminders.json") @@ -53,6 +54,13 @@ class _Settings(BaseSettings): raise ValueError(f"LOG_LEVEL должен быть одним из: {', '.join(allowed)}") return v.lower() + @field_validator("AFK_NICKNAME") + def validate_afk_nickname(cls, v: str) -> str: + # если пустая строка или None, используем дефолт "AFK" + if not v or not v.strip(): + return "AFK" + return v.strip() + @field_validator("WARNINGS_FILE", "REMINDERS_FILE", "BLACKLIST_FILE", "LOG_FILE_NAME", mode="before") def validate_paths(cls, v) -> Optional[Path]: return Path(v) if isinstance(v, str) else v