Админам: персональное отключение уведомлений от клиентов

В админ-панели кнопка «Уведомления: вкл 🔔 / выкл 🔕» — админ (в т.ч. из ADMIN_IDS/
супер-админ) может лично отключить приём уведомлений о заявках и сообщениях клиентов.
Флаг notify_muted хранится в профиле; _notify_operators пропускает замьюченных
получателей (у остальных операторов уведомления идут как обычно).

pytest 37 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-07-26 10:37:58 +03:00
parent f1c984ed69
commit 81c1162ca1
3 changed files with 40 additions and 4 deletions
+23 -3
View File
@@ -202,8 +202,10 @@ class VKBotApplication:
return ids if ids else [config.OPERATOR_PEER_ID] return ids if ids else [config.OPERATOR_PEER_ID]
def _notify_operators(self, message: str): def _notify_operators(self, message: str):
"""Отправляет сообщение всем операторам из списка.""" """Отправляет сообщение операторам из списка (кроме тех, кто отключил себе уведомления)."""
for op_id in self._operator_ids(): for op_id in self._operator_ids():
if db.get_profile(op_id).get("notify_muted"):
continue
self.send_message(op_id, message) self.send_message(op_id, message)
def _vk_name(self, user_id: int) -> Optional[str]: def _vk_name(self, user_id: int) -> Optional[str]:
@@ -256,6 +258,7 @@ class VKBotApplication:
def _send_admin_panel(self, user_id: int): def _send_admin_panel(self, user_id: int):
"""Главный экран админ-панели с кнопками разделов.""" """Главный экран админ-панели с кнопками разделов."""
is_super = self._is_super_admin(user_id) is_super = self._is_super_admin(user_id)
muted = bool(db.get_profile(user_id).get("notify_muted"))
extra = "\n• 👑 Администраторы — добавить/удалить (только супер-админ)" if is_super else "" extra = "\n• 👑 Администраторы — добавить/удалить (только супер-админ)" if is_super else ""
self.send_message( self.send_message(
user_id, user_id,
@@ -264,11 +267,25 @@ class VKBotApplication:
"• 📊 Статистика — заявки и диалоги\n" "• 📊 Статистика — заявки и диалоги\n"
"• 💬 Быстрые ответы — авто-ответы по ключевым словам\n" "• 💬 Быстрые ответы — авто-ответы по ключевым словам\n"
"• 📣 Рассылка — сообщение подписчикам\n" "• 📣 Рассылка — сообщение подписчикам\n"
"• 🔗 Ссылки — куда ведут кнопки записи/оплаты" "• 🔗 Ссылки — куда ведут кнопки записи/оплаты\n"
"• 🔔 Уведомления — вкл/выкл уведомления о заявках лично для вас"
+ extra, + extra,
keyboard=get_admin_keyboard(is_super=is_super) keyboard=get_admin_keyboard(is_super=is_super, muted=muted)
) )
def _toggle_admin_notifications(self, user_id: int):
"""Переключает лично для этого админа приём уведомлений о заявках/клиентах."""
muted = not bool(db.get_profile(user_id).get("notify_muted"))
db.set_profile_field(user_id, "notify_muted", muted)
if muted:
txt = ("🔕 Уведомления от клиентов ОТКЛЮЧЕНЫ лично для вас.\n"
"Заявки и сообщения от клиентов приходить не будут (у других операторов — как раньше).\n"
"Включить обратно — кнопкой «Уведомления» в админке.")
else:
txt = "🔔 Уведомления от клиентов ВКЛЮЧЕНЫ — заявки снова будут приходить вам."
self.send_message(user_id, txt,
keyboard=get_admin_keyboard(is_super=self._is_super_admin(user_id), muted=muted))
def _send_admins_panel(self, user_id: int): def _send_admins_panel(self, user_id: int):
"""Раздел управления администраторами (только супер-админ).""" """Раздел управления администраторами (только супер-админ)."""
if not self._is_super_admin(user_id): if not self._is_super_admin(user_id):
@@ -454,6 +471,9 @@ class VKBotApplication:
elif text_lower in ["ссылки 🔗", "ссылки", "ссылка"]: elif text_lower in ["ссылки 🔗", "ссылки", "ссылка"]:
self._send_links_admin(user_id) self._send_links_admin(user_id)
return return
elif text_lower in ["уведомления: вкл 🔔", "уведомления: выкл 🔕", "уведомления", "уведомления 🔔", "уведомления 🔕"]:
self._toggle_admin_notifications(user_id)
return
elif text_lower in ["✏️ изменить ссылку", "изменить ссылку"]: elif text_lower in ["✏️ изменить ссылку", "изменить ссылку"]:
items = [(k, config.LINK_LABELS.get(k, k)) for k in config.LINK_LABELS] items = [(k, config.LINK_LABELS.get(k, k)) for k in config.LINK_LABELS]
db.update_user_state(user_id, "ADMIN_LINK_PICK") db.update_user_state(user_id, "ADMIN_LINK_PICK")
+4 -1
View File
@@ -142,7 +142,7 @@ def get_baza_prices_keyboard() -> str:
return keyboard.get_keyboard() return keyboard.get_keyboard()
def get_admin_keyboard(is_super: bool = False) -> str: def get_admin_keyboard(is_super: bool = False, muted: bool = False) -> str:
"""Клавиатура админ-панели с разделами. Супер-админу — раздел «Администраторы».""" """Клавиатура админ-панели с разделами. Супер-админу — раздел «Администраторы»."""
keyboard = VkKeyboard(one_time=False) keyboard = VkKeyboard(one_time=False)
keyboard.add_button("Статистика 📊", color=VkKeyboardColor.SECONDARY) keyboard.add_button("Статистика 📊", color=VkKeyboardColor.SECONDARY)
@@ -151,6 +151,9 @@ def get_admin_keyboard(is_super: bool = False) -> str:
keyboard.add_button("Рассылка 📣", color=VkKeyboardColor.SECONDARY) keyboard.add_button("Рассылка 📣", color=VkKeyboardColor.SECONDARY)
keyboard.add_button("Ссылки 🔗", color=VkKeyboardColor.SECONDARY) keyboard.add_button("Ссылки 🔗", color=VkKeyboardColor.SECONDARY)
keyboard.add_line() keyboard.add_line()
keyboard.add_button("Уведомления: выкл 🔕" if muted else "Уведомления: вкл 🔔",
color=VkKeyboardColor.SECONDARY)
keyboard.add_line()
if is_super: if is_super:
keyboard.add_button("Администраторы 👑", color=VkKeyboardColor.SECONDARY) keyboard.add_button("Администраторы 👑", color=VkKeyboardColor.SECONDARY)
keyboard.add_line() keyboard.add_line()
+13
View File
@@ -213,6 +213,19 @@ def test_admin_edit_link(app):
assert app._link("group_4") == "https://infocyber.pro/pay/group4" assert app._link("group_4") == "https://infocyber.pro/pay/group4"
def test_admin_mute_notifications(app):
"""Админ может отключить себе уведомления от клиентов — _notify_operators его пропускает."""
op = _admin()
bot.config.OPERATOR_PEER_IDS = [op]
app._sent.clear(); app._notify_operators("тест-уведомление")
assert any("тест-уведомление" in s["msg"] for s in app._sent) # по умолчанию доходит
app.handle_message(op, "Админка ⚙️")
app.handle_message(op, "Уведомления: вкл 🔔") # переключаем в «выкл»
assert bot.db.get_profile(op).get("notify_muted") is True
app._sent.clear(); app._notify_operators("второе уведомление")
assert not any("второе уведомление" in s["msg"] for s in app._sent) # больше не доходит
def test_admin_faq_crud(app): def test_admin_faq_crud(app):
a = _admin() a = _admin()
app.handle_message(a, "Быстрые ответы 💬") app.handle_message(a, "Быстрые ответы 💬")