From 6790426ba2c1a326a82e3d976e82d394bc81da3e Mon Sep 17 00:00:00 2001 From: miloszowi Date: Fri, 12 Nov 2021 12:23:54 +0100 Subject: [PATCH 1/6] Dynamic mentioning added, removed some unwanted properties, updated README --- CHANGELOG.md | 6 ++++ README.md | 36 ++++++++++++++++++++---- src/bot/handler/__init__.py | 2 +- src/bot/handler/abstractHandler.py | 3 ++ src/bot/handler/dynamicMentionHandler.py | 28 ++++++++++++++++++ src/bot/handler/everyoneHandler.py | 5 +--- src/bot/message/inboundMessage.py | 10 +++++++ src/config/contents.py | 13 +++++---- src/repository/chatRepository.py | 13 ++++++--- src/validator/groupNameValidator.py | 4 +++ 10 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 src/bot/handler/dynamicMentionHandler.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8046c8a..4e7b089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Change Log All notable changes to this project will be documented in this file. +## [0.3.0] - 12.11.2021 +### Added +- Dynamic mentioning by `@` character +### Changed +- `start` text +- Group name validation - those are forbidden now - `all`, `channel`, `chat`, `everyone`, `group`, `here` ## [0.2.0] - 26.10.2021 ### Added - Inline Mode for `join`, `leave` & `everyone` diff --git a/README.md b/README.md index 2204f60..8fcbbf1 100755 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ # Contents * [Description](#description) +* [Usage](#usage) +* [Dynamic Mentioning](#dynamic-mentioning) * [Commands](#commands) * [`/join`](#join) * [`/leave`](#leave) @@ -12,8 +14,7 @@ * [`/groups`](#groups) * [`/start`](#start) * [Example command flow](#example-command-flow) -* [Inline Mode](#inline-mode) - * [Usage](#usage) +* [Inline Mode Usage](#inline-mode-usage) * [Getting started.](#getting-started) * [Requirements](#requirements) * [Installation](#installation) @@ -24,6 +25,32 @@ Everyone Mention Bot is simple, but useful telegram bot to gather group members You can create groups per chat to mention every user that joined the group by calling one command instead of mentioning them one by one. +## Usage +First, users need to join the group to let mentioning them, to do that, they simply need to join specific group. +It can be done in 2 ways: +- Command [`/join`](#join) +- [Inline Mode](#inline-mode-usage) + +Users that have joined the group can be mentioned in 3 ways: +- [Dynaminc Mentioning](#dynamic-mentioning) by `@`, for example `@everyone` +- Command [`/everyone`](#everyone) +- [Inline Mode](#inline-mode-usage) + +To leave the group use one of the two ways: +- Command [`/leave`](#leave) +- [Inline Mode](#inline-mode-usage) + +To display available groups: +- Command [`/groups`](#groups) + +## Dynamic mentioning +You can use `@` character (as you would mention a user) to mention specific group. + +All the below will mention users from `default` group. + +`@all`, `@channel`, `@chat`, `@everyone`, `@group`, `@here`. + +If you did create a group named `gaming`, you can simply use `@gaming` in your text to mention them all. ## Commands *Important*: `{group-name}` is not required, if not given, it will be set to `default`. ### `/join` @@ -85,10 +112,7 @@ Start & Help message ### Example command flow ![example command flow](docs/flow_command.png) -## Inline Mode -Using Inline Mode is recommended because policy of bots with privacy mode enabled (https://core.telegram.org/bots/faq#what-messages-will-my-bot-get) says that command trigger is sent (without mentioning the bot) only to the last mentioned bot. So if you do have multiple bots in current chat, I might not receive your command! - -### Usage +### Inline Mode Usage To use inline mode, type `@everyone_mention_bot` in telegram message input or click on the `Inline Mode` button from `/start` command. ![inline popup](docs/inline_mode_1.png) diff --git a/src/bot/handler/__init__.py b/src/bot/handler/__init__.py index 3a55bbb..b5eceda 100644 --- a/src/bot/handler/__init__.py +++ b/src/bot/handler/__init__.py @@ -1,5 +1,5 @@ __all__ = [ 'abstractHandler', 'everyoneHandler', 'groupsHandler', 'inlineQueryHandler', 'joinHandler', 'leaveHandler', - 'startHandler' + 'startHandler', 'dynamicMentionHandler' ] diff --git a/src/bot/handler/abstractHandler.py b/src/bot/handler/abstractHandler.py index d686924..308ad58 100755 --- a/src/bot/handler/abstractHandler.py +++ b/src/bot/handler/abstractHandler.py @@ -9,6 +9,7 @@ from bot.message.replier import Replier from exception.actionNotAllowedException import ActionNotAllowedException from exception.invalidActionException import InvalidActionException from exception.invalidArgumentException import InvalidArgumentException +from exception.notFoundException import NotFoundException from logger import Logger @@ -30,6 +31,8 @@ class AbstractHandler: Logger.action(self.inbound, self.action) except (InvalidActionException, InvalidArgumentException, ActionNotAllowedException) as e: Replier.markdown(update, str(e)) + except NotFoundException: + pass # probably just mentioning user except Exception as e: Logger.exception(e) diff --git a/src/bot/handler/dynamicMentionHandler.py b/src/bot/handler/dynamicMentionHandler.py new file mode 100644 index 0000000..d5dfe6e --- /dev/null +++ b/src/bot/handler/dynamicMentionHandler.py @@ -0,0 +1,28 @@ +import re + +from telegram.ext import Filters, MessageHandler +from telegram.ext.callbackcontext import CallbackContext +from telegram.update import Update + +from bot.handler.abstractHandler import AbstractHandler +from bot.message.replier import Replier +from repository.chatRepository import ChatRepository +from utils.messageBuilder import MessageBuilder + + +class DynamicMentionHandler(AbstractHandler): + bot_handler: MessageHandler + chat_repository: ChatRepository + action: str = 'dynamic-mention' + + def __init__(self) -> None: + self.bot_handler = MessageHandler( + Filters.regex(re.compile(r'@[^ ]')), + self.wrap + ) + self.chat_repository = ChatRepository() + + def handle(self, update: Update, context: CallbackContext) -> None: + users = self.chat_repository.get_users_for_group(self.inbound) + + Replier.markdown(update, MessageBuilder.mention_message(users)) diff --git a/src/bot/handler/everyoneHandler.py b/src/bot/handler/everyoneHandler.py index a31feac..047ae65 100755 --- a/src/bot/handler/everyoneHandler.py +++ b/src/bot/handler/everyoneHandler.py @@ -8,24 +8,21 @@ from config.contents import mention_failed from exception.invalidActionException import InvalidActionException from exception.notFoundException import NotFoundException from repository.chatRepository import ChatRepository -from repository.userRepository import UserRepository from utils.messageBuilder import MessageBuilder class EveryoneHandler(AbstractHandler): bot_handler: CommandHandler chat_repository: ChatRepository - user_repository: UserRepository action: str = 'everyone' def __init__(self) -> None: self.bot_handler = CommandHandler(self.action, self.wrap) self.chat_repository = ChatRepository() - self.user_repository = UserRepository() def handle(self, update: Update, context: CallbackContext) -> None: try: - users = self.chat_repository.get_users_for_group(self.inbound.chat_id, self.inbound.group_name) + users = self.chat_repository.get_users_for_group(self.inbound) Replier.markdown(update, MessageBuilder.mention_message(users)) except NotFoundException as e: diff --git a/src/bot/message/inboundMessage.py b/src/bot/message/inboundMessage.py index c0f5505..a50865e 100644 --- a/src/bot/message/inboundMessage.py +++ b/src/bot/message/inboundMessage.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass import names +import re from telegram.ext.callbackcontext import CallbackContext from telegram.update import Update @@ -27,11 +28,20 @@ class InboundMessage: chat_id = str(update.effective_chat.id) group_name = InboundMessage.default_group + # done upon resolving a command action if context.args and context.args[0] and group_specific: group_name = str(context.args[0]).lower() GroupNameValidator.validate(group_name) + # done upon resolving a message handler action + if '@' in update.message.text: + searched_message_part = [part for part in update.message.text.split(' ') if '@' in part][0] + group_name = re.sub(r'\W+', '', searched_message_part) + + if group_name in GroupNameValidator.FORBIDDEN_GROUP_NAMES: + group_name = InboundMessage.default_group + username = update.effective_user.username or update.effective_user.first_name if not username: diff --git a/src/config/contents.py b/src/config/contents.py index 952a2bc..ac1eac7 100755 --- a/src/config/contents.py +++ b/src/config/contents.py @@ -10,13 +10,16 @@ no_groups = 'There are no groups for this chat' start_text = """ Hello! @everyone_mention_bot here. - -Description: -I do not have access to your messages! I am here to help you with multiple user mentions. Usage: -Users that joined the group by /join command, can be mentioned after calling /everyone command. +Users that joined the group by /join command, +can be mentioned after typing one of those in your message: +@all, @channel, @chat, @everyone, @group or @here. + +If you did create a group named gaming, simply use @gaming to call users from that group. + +You can also use /everyone command. Commands:
/join {group-name}
@@ -36,6 +39,4 @@ Show start & help text Please note {group-name} is not required, default if not given. - -If your chat does have multiple bots I might not receive your command according to policy of bots with privacy mode enabled - use Inline Mode to avoid this. """ diff --git a/src/repository/chatRepository.py b/src/repository/chatRepository.py index c8786f3..83a7a70 100644 --- a/src/repository/chatRepository.py +++ b/src/repository/chatRepository.py @@ -39,12 +39,17 @@ class ChatRepository(AbstractRepository): return Chat.from_mongo_document(chat) - def get_users_for_group(self, chat_id: str, group: str) -> Iterable[User]: - chat = self.get(chat_id) - if not chat.groups.get(group): + def get_users_for_group(self, inbound: InboundMessage) -> Iterable[User]: + chat = self.get(inbound.chat_id) + if not chat.groups.get(inbound.group_name): raise NotFoundException - return [self.user_repository.get(user_id) for user_id in chat.groups.get(group)] + users = [self.user_repository.get(user_id) for user_id in chat.groups.get(inbound.group_name) if user_id != inbound.user_id] + + if not users: + raise NotFoundException + + return users def save(self, chat: Chat) -> None: self.database_client.save( diff --git a/src/validator/groupNameValidator.py b/src/validator/groupNameValidator.py index c910ae6..82ec899 100644 --- a/src/validator/groupNameValidator.py +++ b/src/validator/groupNameValidator.py @@ -5,6 +5,7 @@ from exception.invalidArgumentException import InvalidArgumentException class GroupNameValidator: MAX_GROUP_NAME_LENGTH: int = 40 + FORBIDDEN_GROUP_NAMES = ['all', 'channel', 'chat', 'everyone', 'group', 'here'] @staticmethod def validate(group: str) -> None: @@ -15,3 +16,6 @@ class GroupNameValidator: if len(group) > GroupNameValidator.MAX_GROUP_NAME_LENGTH: raise InvalidArgumentException(re.escape(f'Group name length can not be greater than {GroupNameValidator.MAX_GROUP_NAME_LENGTH}.')) + + if group in GroupNameValidator.FORBIDDEN_GROUP_NAMES: + raise InvalidArgumentException(re.escape(f'This group name is forbidden, please try with other name.')) From 568ac15b5eb8ae8e5520412666c8a6459de713ef Mon Sep 17 00:00:00 2001 From: miloszowi Date: Fri, 12 Nov 2021 13:15:00 +0100 Subject: [PATCH 2/6] dynamic mentioning group name to lowercase, updated CHANGELOG --- CHANGELOG.md | 2 ++ src/bot/message/inboundMessage.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7b089..24d47a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ All notable changes to this project will be documented in this file. ### Changed - `start` text - Group name validation - those are forbidden now - `all`, `channel`, `chat`, `everyone`, `group`, `here` +### Deleted +- mentioning user that performed `/everyone` or dynamic mention in bot response ## [0.2.0] - 26.10.2021 ### Added - Inline Mode for `join`, `leave` & `everyone` diff --git a/src/bot/message/inboundMessage.py b/src/bot/message/inboundMessage.py index a50865e..4aa9350 100644 --- a/src/bot/message/inboundMessage.py +++ b/src/bot/message/inboundMessage.py @@ -37,7 +37,7 @@ class InboundMessage: # done upon resolving a message handler action if '@' in update.message.text: searched_message_part = [part for part in update.message.text.split(' ') if '@' in part][0] - group_name = re.sub(r'\W+', '', searched_message_part) + group_name = re.sub(r'\W+', '', searched_message_part).lower() if group_name in GroupNameValidator.FORBIDDEN_GROUP_NAMES: group_name = InboundMessage.default_group From 48ceab008d28379ae71bb2966d8b904693d83bfd Mon Sep 17 00:00:00 2001 From: miloszowi Date: Fri, 12 Nov 2021 14:20:51 +0100 Subject: [PATCH 3/6] fixed commands with bot mention causing invalid group name --- src/bot/message/inboundMessage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot/message/inboundMessage.py b/src/bot/message/inboundMessage.py index 4aa9350..8267eee 100644 --- a/src/bot/message/inboundMessage.py +++ b/src/bot/message/inboundMessage.py @@ -35,7 +35,7 @@ class InboundMessage: GroupNameValidator.validate(group_name) # done upon resolving a message handler action - if '@' in update.message.text: + if '@' in update.message.text and update.message.text[0] != '/': searched_message_part = [part for part in update.message.text.split(' ') if '@' in part][0] group_name = re.sub(r'\W+', '', searched_message_part).lower() From df374e31bae884526da390deaa25322eae3deb37 Mon Sep 17 00:00:00 2001 From: miloszowi Date: Mon, 15 Nov 2021 09:20:43 +0100 Subject: [PATCH 4/6] fixed NoneType upon telegram message edit --- src/bot/message/inboundMessage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot/message/inboundMessage.py b/src/bot/message/inboundMessage.py index 8267eee..58c0573 100644 --- a/src/bot/message/inboundMessage.py +++ b/src/bot/message/inboundMessage.py @@ -35,7 +35,7 @@ class InboundMessage: GroupNameValidator.validate(group_name) # done upon resolving a message handler action - if '@' in update.message.text and update.message.text[0] != '/': + if update.message and '@' in update.message.text and update.message.text[0] != '/': searched_message_part = [part for part in update.message.text.split(' ') if '@' in part][0] group_name = re.sub(r'\W+', '', searched_message_part).lower() From a02c09d19d60e192b8bff343a25d0c1df6186abd Mon Sep 17 00:00:00 2001 From: miloszowi Date: Wed, 24 Nov 2021 14:28:09 +0100 Subject: [PATCH 5/6] fixed problem upon editing message with dynamic mentions causing mentioning default group --- src/bot/message/inboundMessage.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bot/message/inboundMessage.py b/src/bot/message/inboundMessage.py index 58c0573..d41a013 100644 --- a/src/bot/message/inboundMessage.py +++ b/src/bot/message/inboundMessage.py @@ -7,6 +7,7 @@ import re from telegram.ext.callbackcontext import CallbackContext from telegram.update import Update +from exception.invalidActionException import InvalidActionException from validator.accessValidator import AccessValidator from validator.groupNameValidator import GroupNameValidator @@ -25,6 +26,9 @@ class InboundMessage: user_id = str(update.effective_user.id) AccessValidator.validate(user_id) + if update.edited_message: + raise InvalidActionException + chat_id = str(update.effective_chat.id) group_name = InboundMessage.default_group @@ -35,7 +39,7 @@ class InboundMessage: GroupNameValidator.validate(group_name) # done upon resolving a message handler action - if update.message and '@' in update.message.text and update.message.text[0] != '/': + if '@' in update.message.text and update.message.text[0] != '/': searched_message_part = [part for part in update.message.text.split(' ') if '@' in part][0] group_name = re.sub(r'\W+', '', searched_message_part).lower() From 4140098594b142370604034f7e445a83b8f3726d Mon Sep 17 00:00:00 2001 From: miloszowi Date: Thu, 10 Mar 2022 16:04:38 +0100 Subject: [PATCH 6/6] fixed default group mention if there was a @ sign in any command argument --- src/bot/message/inboundMessage.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/bot/message/inboundMessage.py b/src/bot/message/inboundMessage.py index d41a013..685dfe8 100644 --- a/src/bot/message/inboundMessage.py +++ b/src/bot/message/inboundMessage.py @@ -26,9 +26,8 @@ class InboundMessage: user_id = str(update.effective_user.id) AccessValidator.validate(user_id) - if update.edited_message: - raise InvalidActionException - + message_content = update.edited_message.text if update.edited_message else update.message.text + message_content = message_content.replace("\n", " ") chat_id = str(update.effective_chat.id) group_name = InboundMessage.default_group @@ -39,8 +38,8 @@ class InboundMessage: GroupNameValidator.validate(group_name) # done upon resolving a message handler action - if '@' in update.message.text and update.message.text[0] != '/': - searched_message_part = [part for part in update.message.text.split(' ') if '@' in part][0] + if '@' in message_content: + searched_message_part = [part for part in message_content.split(' ') if '@' in part][0] group_name = re.sub(r'\W+', '', searched_message_part).lower() if group_name in GroupNameValidator.FORBIDDEN_GROUP_NAMES: