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 <noreply@anthropic.com>
This commit is contained in:
bvn13 2026-06-28 18:23:16 +03:00
parent df2cab6b2b
commit 51bd0b4b03
2 changed files with 22 additions and 6 deletions

View File

@ -63,7 +63,7 @@ class Bot():
async def onMessageSub(event): async def onMessageSub(event):
if not self.__is_admin(event.sender_id): if not self.__is_admin(event.sender_id):
raise events.StopPropagation 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 event.reply(reply)
await self.__bot.send_read_acknowledge(event.chat_id, event.message) await self.__bot.send_read_acknowledge(event.chat_id, event.message)
raise events.StopPropagation raise events.StopPropagation
@ -116,7 +116,7 @@ class Bot():
def __is_admin(self, user_id) -> bool: def __is_admin(self, user_id) -> bool:
return user_id == self.__owner_id or user_id in self.__admin_ids 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() parts = text.strip().split()
if len(parts) < 2: if len(parts) < 2:
return self.__sub_help() return self.__sub_help()
@ -135,9 +135,13 @@ class Bot():
if ftype not in ('contain', 'regexp'): if ftype not in ('contain', 'regexp'):
return "filter type must be 'contain' or 'regexp'" return "filter type must be 'contain' or 'regexp'"
value = tokens[5] value = tokens[5]
rule = self.__config.add_subscription(src, ftype, value, dst) src_title = await self.__get_chat_title(src)
return "Added #{0}: {1} [{2}] '{3}' -> {4}".format( dst_title = await self.__get_chat_title(dst)
rule['id'], src, ftype, value, 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'): if cmd in ('del', 'rm', 'delete'):
sub_id = int(parts[2]) sub_id = int(parts[2])
ok = self.__config.remove_subscription(sub_id) ok = self.__config.remove_subscription(sub_id)
@ -154,6 +158,13 @@ class Bot():
return self.__sub_help() return self.__sub_help()
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: def __sub_list(self) -> str:
subs = self.__config.list_subscriptions() subs = self.__config.list_subscriptions()
if not subs: if not subs:

View File

@ -41,10 +41,15 @@ class Config():
return result return result
def add_subscription(self, src_chat_id: int, filter_type: str, filter_value: str, 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: with self.__lock:
on_message = self.__config.setdefault('onMessage', {}) on_message = self.__config.setdefault('onMessage', {})
rules = on_message.setdefault(str(src_chat_id), []) 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 = { rule = {
'id': self.__next_id(), 'id': self.__next_id(),
'enabled': True, 'enabled': True,