From 51bd0b4b03a6c05e76237bfb553d65a4e1d7b65c Mon Sep 17 00:00:00 2001 From: bvn13 Date: Sun, 28 Jun 2026 18:23:16 +0300 Subject: [PATCH] Add deduplication and auto-name for subscriptions - Config: add_subscription returns None on duplicate (same src, filter, dst) - Bot: resolve chat titles via get_entity, name rule as "src -> dst (filter_value)"; show duplicate message instead of adding Co-Authored-By: Claude Sonnet 4.6 --- src/bot/bot.py | 21 ++++++++++++++++----- src/settings/config.py | 7 ++++++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/bot/bot.py b/src/bot/bot.py index d4cf3a3..725bea8 100644 --- a/src/bot/bot.py +++ b/src/bot/bot.py @@ -63,7 +63,7 @@ class Bot(): async def onMessageSub(event): if not self.__is_admin(event.sender_id): raise events.StopPropagation - reply = self.__handle_sub_command(event.message.text or "") + reply = await self.__handle_sub_command(event.message.text or "") await event.reply(reply) await self.__bot.send_read_acknowledge(event.chat_id, event.message) raise events.StopPropagation @@ -116,7 +116,7 @@ class Bot(): def __is_admin(self, user_id) -> bool: return user_id == self.__owner_id or user_id in self.__admin_ids - def __handle_sub_command(self, text: str) -> str: + async def __handle_sub_command(self, text: str) -> str: parts = text.strip().split() if len(parts) < 2: return self.__sub_help() @@ -135,9 +135,13 @@ class Bot(): if ftype not in ('contain', 'regexp'): return "filter type must be 'contain' or 'regexp'" value = tokens[5] - rule = self.__config.add_subscription(src, ftype, value, dst) - return "Added #{0}: {1} [{2}] '{3}' -> {4}".format( - rule['id'], src, ftype, value, dst) + src_title = await self.__get_chat_title(src) + dst_title = await self.__get_chat_title(dst) + name = "{0} -> {1} ({2})".format(src_title, dst_title, value) + rule = self.__config.add_subscription(src, ftype, value, dst, name=name) + if rule is None: + return "Already exists: {0} [{1}] '{2}' -> {3}".format(src, ftype, value, dst) + return "Added #{0}: {1}".format(rule['id'], rule.get('name', '')) if cmd in ('del', 'rm', 'delete'): sub_id = int(parts[2]) ok = self.__config.remove_subscription(sub_id) @@ -154,6 +158,13 @@ class Bot(): return self.__sub_help() return self.__sub_help() + async def __get_chat_title(self, chat_id: int) -> str: + try: + entity = await self.__bot.get_entity(chat_id) + return getattr(entity, 'title', None) or getattr(entity, 'username', None) or str(chat_id) + except Exception: + return str(chat_id) + def __sub_list(self) -> str: subs = self.__config.list_subscriptions() if not subs: diff --git a/src/settings/config.py b/src/settings/config.py index b7b8fce..a02e7b4 100644 --- a/src/settings/config.py +++ b/src/settings/config.py @@ -41,10 +41,15 @@ class Config(): return result def add_subscription(self, src_chat_id: int, filter_type: str, filter_value: str, - dst_chat_id: int, name: Optional[str] = None) -> dict: + dst_chat_id: int, name: Optional[str] = None) -> Optional[dict]: with self.__lock: on_message = self.__config.setdefault('onMessage', {}) rules = on_message.setdefault(str(src_chat_id), []) + for existing in rules: + if (existing.get('filter', {}).get('type') == filter_type and + existing.get('filter', {}).get('value') == filter_value and + existing.get('action', {}).get('chatId') == dst_chat_id): + return None rule = { 'id': self.__next_id(), 'enabled': True,