Merge pull request #5 from miloszowi/develop

0.3.0 Release
This commit is contained in:
Miłosz Guglas 2022-03-10 16:08:02 +01:00 committed by GitHub
commit a6b441b197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 104 additions and 21 deletions

View File

@ -1,6 +1,14 @@
# Change Log # Change Log
All notable changes to this project will be documented in this file. 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`
### Deleted
- mentioning user that performed `/everyone` or dynamic mention in bot response
## [0.2.0] - 26.10.2021 ## [0.2.0] - 26.10.2021
### Added ### Added
- Inline Mode for `join`, `leave` & `everyone` - Inline Mode for `join`, `leave` & `everyone`

View File

@ -5,6 +5,8 @@
# Contents # Contents
* [Description](#description) * [Description](#description)
* [Usage](#usage)
* [Dynamic Mentioning](#dynamic-mentioning)
* [Commands](#commands) * [Commands](#commands)
* [`/join`](#join) * [`/join`](#join)
* [`/leave`](#leave) * [`/leave`](#leave)
@ -12,8 +14,7 @@
* [`/groups`](#groups) * [`/groups`](#groups)
* [`/start`](#start) * [`/start`](#start)
* [Example command flow](#example-command-flow) * [Example command flow](#example-command-flow)
* [Inline Mode](#inline-mode) * [Inline Mode Usage](#inline-mode-usage)
* [Usage](#usage)
* [Getting started.](#getting-started) * [Getting started.](#getting-started)
* [Requirements](#requirements) * [Requirements](#requirements)
* [Installation](#installation) * [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. 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 ## Commands
*Important*: `{group-name}` is not required, if not given, it will be set to `default`. *Important*: `{group-name}` is not required, if not given, it will be set to `default`.
### `/join` ### `/join`
@ -85,10 +112,7 @@ Start & Help message
### Example command flow ### Example command flow
![example command flow](docs/flow_command.png) ![example command flow](docs/flow_command.png)
## Inline Mode ### Inline Mode Usage
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
To use inline mode, type `@everyone_mention_bot` in telegram message input or click on the `Inline Mode` button from `/start` command. 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) ![inline popup](docs/inline_mode_1.png)

View File

@ -1,5 +1,5 @@
__all__ = [ __all__ = [
'abstractHandler', 'everyoneHandler', 'groupsHandler', 'abstractHandler', 'everyoneHandler', 'groupsHandler',
'inlineQueryHandler', 'joinHandler', 'leaveHandler', 'inlineQueryHandler', 'joinHandler', 'leaveHandler',
'startHandler' 'startHandler', 'dynamicMentionHandler'
] ]

View File

@ -9,6 +9,7 @@ from bot.message.replier import Replier
from exception.actionNotAllowedException import ActionNotAllowedException from exception.actionNotAllowedException import ActionNotAllowedException
from exception.invalidActionException import InvalidActionException from exception.invalidActionException import InvalidActionException
from exception.invalidArgumentException import InvalidArgumentException from exception.invalidArgumentException import InvalidArgumentException
from exception.notFoundException import NotFoundException
from logger import Logger from logger import Logger
@ -30,6 +31,8 @@ class AbstractHandler:
Logger.action(self.inbound, self.action) Logger.action(self.inbound, self.action)
except (InvalidActionException, InvalidArgumentException, ActionNotAllowedException) as e: except (InvalidActionException, InvalidArgumentException, ActionNotAllowedException) as e:
Replier.markdown(update, str(e)) Replier.markdown(update, str(e))
except NotFoundException:
pass # probably just mentioning user
except Exception as e: except Exception as e:
Logger.exception(e) Logger.exception(e)

View File

@ -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))

View File

@ -8,24 +8,21 @@ from config.contents import mention_failed
from exception.invalidActionException import InvalidActionException from exception.invalidActionException import InvalidActionException
from exception.notFoundException import NotFoundException from exception.notFoundException import NotFoundException
from repository.chatRepository import ChatRepository from repository.chatRepository import ChatRepository
from repository.userRepository import UserRepository
from utils.messageBuilder import MessageBuilder from utils.messageBuilder import MessageBuilder
class EveryoneHandler(AbstractHandler): class EveryoneHandler(AbstractHandler):
bot_handler: CommandHandler bot_handler: CommandHandler
chat_repository: ChatRepository chat_repository: ChatRepository
user_repository: UserRepository
action: str = 'everyone' action: str = 'everyone'
def __init__(self) -> None: def __init__(self) -> None:
self.bot_handler = CommandHandler(self.action, self.wrap) self.bot_handler = CommandHandler(self.action, self.wrap)
self.chat_repository = ChatRepository() self.chat_repository = ChatRepository()
self.user_repository = UserRepository()
def handle(self, update: Update, context: CallbackContext) -> None: def handle(self, update: Update, context: CallbackContext) -> None:
try: 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)) Replier.markdown(update, MessageBuilder.mention_message(users))
except NotFoundException as e: except NotFoundException as e:

View File

@ -3,9 +3,11 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import names import names
import re
from telegram.ext.callbackcontext import CallbackContext from telegram.ext.callbackcontext import CallbackContext
from telegram.update import Update from telegram.update import Update
from exception.invalidActionException import InvalidActionException
from validator.accessValidator import AccessValidator from validator.accessValidator import AccessValidator
from validator.groupNameValidator import GroupNameValidator from validator.groupNameValidator import GroupNameValidator
@ -24,14 +26,25 @@ class InboundMessage:
user_id = str(update.effective_user.id) user_id = str(update.effective_user.id)
AccessValidator.validate(user_id) AccessValidator.validate(user_id)
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) chat_id = str(update.effective_chat.id)
group_name = InboundMessage.default_group group_name = InboundMessage.default_group
# done upon resolving a command action
if context.args and context.args[0] and group_specific: if context.args and context.args[0] and group_specific:
group_name = str(context.args[0]).lower() group_name = str(context.args[0]).lower()
GroupNameValidator.validate(group_name) GroupNameValidator.validate(group_name)
# done upon resolving a message handler action
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:
group_name = InboundMessage.default_group
username = update.effective_user.username or update.effective_user.first_name username = update.effective_user.username or update.effective_user.first_name
if not username: if not username:

View File

@ -10,13 +10,16 @@ no_groups = 'There are no groups for this chat'
start_text = """ start_text = """
Hello! Hello!
@everyone_mention_bot here. @everyone_mention_bot here.
<b>Description</b>:
I <b>do not</b> have access to your messages!
I am here to help you with multiple user mentions. I am here to help you with multiple user mentions.
<b>Usage</b>: <b>Usage</b>:
Users that joined the group by <code>/join</code> command, can be mentioned after calling <code>/everyone</code> command. Users that joined the group by <code>/join</code> command,
can be mentioned after typing one of those in your message:
<code>@all</code>, <code>@channel</code>, <code>@chat</code>, <code>@everyone</code>, <code>@group</code> or <code>@here</code>.
If you did create a group named <code>gaming</code>, simply use <code>@gaming</code> to call users from that group.
You can also use <code>/everyone</code> command.
<b>Commands</b>: <b>Commands</b>:
<pre>/join {group-name}</pre> <pre>/join {group-name}</pre>
@ -36,6 +39,4 @@ Show start & help text
<b>Please note</b> <b>Please note</b>
<code>{group-name}</code> is not required, <code>default</code> if not given. <code>{group-name}</code> is not required, <code>default</code> if not given.
If your chat does have multiple bots <b>I might not receive your command</b> according to <a href="https://core.telegram.org/bots/faq#what-messages-will-my-bot-get">policy of bots with privacy mode enabled</a> - use <code>Inline Mode</code> to avoid this.
""" """

View File

@ -39,12 +39,17 @@ class ChatRepository(AbstractRepository):
return Chat.from_mongo_document(chat) return Chat.from_mongo_document(chat)
def get_users_for_group(self, chat_id: str, group: str) -> Iterable[User]: def get_users_for_group(self, inbound: InboundMessage) -> Iterable[User]:
chat = self.get(chat_id) chat = self.get(inbound.chat_id)
if not chat.groups.get(group): if not chat.groups.get(inbound.group_name):
raise NotFoundException 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: def save(self, chat: Chat) -> None:
self.database_client.save( self.database_client.save(

View File

@ -5,6 +5,7 @@ from exception.invalidArgumentException import InvalidArgumentException
class GroupNameValidator: class GroupNameValidator:
MAX_GROUP_NAME_LENGTH: int = 40 MAX_GROUP_NAME_LENGTH: int = 40
FORBIDDEN_GROUP_NAMES = ['all', 'channel', 'chat', 'everyone', 'group', 'here']
@staticmethod @staticmethod
def validate(group: str) -> None: def validate(group: str) -> None:
@ -15,3 +16,6 @@ class GroupNameValidator:
if len(group) > GroupNameValidator.MAX_GROUP_NAME_LENGTH: 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}.')) 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.'))