mongodb to 5.0.2 update, added groups command, added groups logic, changed silent logic

This commit is contained in:
miloszowi 2021-09-28 21:28:33 +02:00
parent bbb1706dbe
commit 7c355d6cbe
16 changed files with 182 additions and 31 deletions

View File

@ -3,7 +3,7 @@ version: "3.6"
services: services:
database: database:
image: mongo:4.0.8 image: mongo:5.0.2
restart: unless-stopped restart: unless-stopped
env_file: env_file:
- ./docker/config/database.env - ./docker/config/database.env

View File

@ -3,7 +3,7 @@ from telegram.ext.dispatcher import Dispatcher
from config.credentials import BOT_TOKEN from config.credentials import BOT_TOKEN
from handler.abstractHandler import AbstractHandler from handler.abstractHandler import AbstractHandler
from handler import (inHandler, mentionHandler, outHandler) from handler import (inHandler, mentionHandler, outHandler, silentMentionHandler, groupsHandler)
class App: class App:

View File

@ -6,3 +6,4 @@ opted_in_failed = re.escape('You already opted-in for everyone-mentions.')
opted_off = re.escape('You have opted-off for everyone-mentions.') opted_off = re.escape('You have opted-off for everyone-mentions.')
opted_off_failed = re.escape('You need to opt-in first before processing this command.') opted_off_failed = re.escape('You need to opt-in first before processing this command.')
mention_failed = re.escape('There are no users to mention.') mention_failed = re.escape('There are no users to mention.')
no_groups = re.escape('There are no groups for this chat.')

View File

@ -1,3 +1,4 @@
from typing import Optional
from urllib.parse import quote_plus from urllib.parse import quote_plus
from config.credentials import (MONGODB_DATABASE, MONGODB_HOSTNAME, from config.credentials import (MONGODB_DATABASE, MONGODB_HOSTNAME,
@ -34,3 +35,6 @@ class Client():
filter, filter,
{ "$set" : data } { "$set" : data }
) )
def aggregate(self, collection, pipeline: list):
return self.database.get_collection(collection).aggregate(pipeline)

12
src/entity/group.py Normal file
View File

@ -0,0 +1,12 @@
from __future__ import annotations
from dataclasses import dataclass
@dataclass
class Group():
chat_id: str
group_name: str
users_count: int
default_name: str = 'default'

View File

@ -1,2 +0,0 @@
class AlreadyExistsException(Exception):
pass

View File

@ -0,0 +1,2 @@
class InvalidArgumentException(Exception):
pass

View File

@ -14,8 +14,11 @@ class AbstractHandler:
@abstractmethod @abstractmethod
def handle(self, update: Update, context: CallbackContext) -> None: raise Exception('handle method is not implemented') def handle(self, update: Update, context: CallbackContext) -> None: raise Exception('handle method is not implemented')
def get_update_data(self, update: Update) -> UpdateData: def get_update_data(self, update: Update, context: CallbackContext) -> UpdateData:
return UpdateData.create_from_update(update) return UpdateData.create_from_arguments(update, context)
def reply(self, update: Update, message: str) -> None: def reply_markdown(self, update: Update, message: str) -> None:
update.effective_message.reply_markdown_v2(text=message) update.effective_message.reply_markdown_v2(text=message)
def reply_html(self, update: Update, html: str) -> None:
update.effective_message.reply_html(text=html)

View File

@ -0,0 +1,40 @@
from typing import Iterable
import prettytable as pt
from config.contents import no_groups
from entity.group import Group
from repository.groupRepository import GroupRepository
from telegram.ext.callbackcontext import CallbackContext
from telegram.ext.commandhandler import CommandHandler
from telegram.update import Update
from handler.abstractHandler import AbstractHandler
class GroupsHandler(AbstractHandler):
bot_handler: CommandHandler
group_repository: GroupRepository
def __init__(self) -> None:
self.bot_handler = CommandHandler('groups', self.handle)
self.group_repository = GroupRepository()
def handle(self, update: Update, context: CallbackContext) -> None:
real_chat_id = str(update.effective_chat.id)
groups = self.group_repository.get_by_chat_id(real_chat_id)
if groups:
return self.reply_html(update, self.build_groups_message(groups))
self.reply_markdown(update, no_groups)
def get_bot_handler(self) -> CommandHandler:
return self.bot_handler
def build_groups_message(self, groups: Iterable[Group]) -> str:
resultTable = pt.PrettyTable(['Name', 'Members'])
resultTable.add_rows([[record.group_name, record.users_count] for record in groups])
return f'<pre>{str(resultTable)}</pre>'

View File

@ -1,4 +1,5 @@
from config.contents import opted_in, opted_in_failed from config.contents import opted_in, opted_in_failed
from exception.invalidArgumentException import InvalidArgumentException
from exception.notFoundException import NotFoundException from exception.notFoundException import NotFoundException
from repository.userRepository import UserRepository from repository.userRepository import UserRepository
from telegram.ext.callbackcontext import CallbackContext from telegram.ext.callbackcontext import CallbackContext
@ -17,14 +18,16 @@ class InHandler(AbstractHandler):
self.user_repository = UserRepository() self.user_repository = UserRepository()
def handle(self, update: Update, context: CallbackContext) -> None: def handle(self, update: Update, context: CallbackContext) -> None:
update_data = self.get_update_data(update) try:
update_data = self.get_update_data(update, context)
except InvalidArgumentException as e:
return self.reply_markdown(update, str(e))
try: try:
user = self.user_repository.get_by_id(update_data.user_id) user = self.user_repository.get_by_id(update_data.user_id)
if user.is_in_chat(update_data.chat_id): if user.is_in_chat(update_data.chat_id):
self.reply(update, opted_in_failed) return self.reply_markdown(update, opted_in_failed)
return
user.add_to_chat(update_data.chat_id) user.add_to_chat(update_data.chat_id)
self.user_repository.save(user) self.user_repository.save(user)
@ -32,7 +35,7 @@ class InHandler(AbstractHandler):
except NotFoundException: except NotFoundException:
self.user_repository.save_by_update_data(update_data) self.user_repository.save_by_update_data(update_data)
self.reply(update, opted_in) self.reply_markdown(update, opted_in)
def get_bot_handler(self) -> CommandHandler: def get_bot_handler(self) -> CommandHandler:
return self.bot_handler return self.bot_handler

View File

@ -2,6 +2,7 @@ from typing import Iterable
from config.contents import mention_failed from config.contents import mention_failed
from entity.user import User from entity.user import User
from exception.invalidArgumentException import InvalidArgumentException
from repository.userRepository import UserRepository from repository.userRepository import UserRepository
from telegram.ext.callbackcontext import CallbackContext from telegram.ext.callbackcontext import CallbackContext
from telegram.ext.commandhandler import CommandHandler from telegram.ext.commandhandler import CommandHandler
@ -13,35 +14,31 @@ from handler.abstractHandler import AbstractHandler
class MentionHandler(AbstractHandler): class MentionHandler(AbstractHandler):
bot_handler: CommandHandler bot_handler: CommandHandler
user_repository: UserRepository user_repository: UserRepository
silent: str = 'silent'
def __init__(self) -> None: def __init__(self) -> None:
self.bot_handler = CommandHandler('everyone', self.handle) self.bot_handler = CommandHandler('everyone', self.handle)
self.user_repository = UserRepository() self.user_repository = UserRepository()
def handle(self, update: Update, context: CallbackContext) -> None: def handle(self, update: Update, context: CallbackContext) -> None:
updateData = self.get_update_data(update) try:
updateData = self.get_update_data(update, context)
except InvalidArgumentException as e:
return self.reply_markdown(update, str(e))
users = self.user_repository.get_all_for_chat(updateData.chat_id) users = self.user_repository.get_all_for_chat(updateData.chat_id)
if users: if users:
self.reply(update, self.build_mention_message(users, self.isSilent(context))) return self.reply_markdown(update, self.build_mention_message(users))
return
self.reply(update, mention_failed) self.reply_markdown(update, mention_failed)
def get_bot_handler(self) -> CommandHandler: def get_bot_handler(self) -> CommandHandler:
return self.bot_handler return self.bot_handler
def build_mention_message(self, users: Iterable[User], silent: bool = False) -> str: def build_mention_message(self, users: Iterable[User]) -> str:
result = '' result = ''
for user in users: for user in users:
if not silent: result += f'*[{user.username}](tg://user?id={user.user_id})* '
result += f'*[{user.username}](tg://user?id={user.user_id})* '
else:
result += f'*{user.username}\({user.user_id}\)*\n'
return result return result
def isSilent(self, context: CallbackContext) -> bool:
return self.silent in context.args

View File

@ -1,4 +1,5 @@
from config.contents import opted_off, opted_off_failed from config.contents import opted_off, opted_off_failed
from exception.invalidArgumentException import InvalidArgumentException
from exception.notFoundException import NotFoundException from exception.notFoundException import NotFoundException
from repository.userRepository import UserRepository from repository.userRepository import UserRepository
from telegram.ext.callbackcontext import CallbackContext from telegram.ext.callbackcontext import CallbackContext
@ -17,7 +18,10 @@ class OutHandler(AbstractHandler):
self.user_repository = UserRepository() self.user_repository = UserRepository()
def handle(self, update: Update, context: CallbackContext) -> None: def handle(self, update: Update, context: CallbackContext) -> None:
updateData = self.get_update_data(update) try:
updateData = self.get_update_data(update, context)
except InvalidArgumentException as e:
return self.reply_markdown(update, str(e))
try: try:
user = self.user_repository.get_by_id(updateData.user_id) user = self.user_repository.get_by_id(updateData.user_id)
@ -25,13 +29,12 @@ class OutHandler(AbstractHandler):
if not user.is_in_chat(updateData.chat_id): if not user.is_in_chat(updateData.chat_id):
raise NotFoundException() raise NotFoundException()
except NotFoundException: except NotFoundException:
self.reply(update, opted_off_failed) return self.reply_markdown(update, opted_off_failed)
return
user.remove_from_chat(updateData.chat_id) user.remove_from_chat(updateData.chat_id)
self.user_repository.save(user) self.user_repository.save(user)
self.reply(update, opted_off) self.reply_markdown(update, opted_off)
def get_bot_handler(self) -> CommandHandler: def get_bot_handler(self) -> CommandHandler:
return self.bot_handler return self.bot_handler

View File

@ -0,0 +1,21 @@
from typing import Iterable
from entity.user import User
from telegram.ext.commandhandler import CommandHandler
from handler.abstractHandler import AbstractHandler
from handler.mentionHandler import MentionHandler
class MentionHandler(MentionHandler, AbstractHandler):
def __init__(self) -> None:
super().__init__()
self.bot_handler = CommandHandler('silent', self.handle)
def build_mention_message(self, users: Iterable[User]) -> str:
result = ''
for user in users:
result += f'*{user.username}\({user.user_id}\)*\n'
return result

View File

@ -1,9 +1,14 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
import re
import names import names
from telegram.ext.callbackcontext import CallbackContext
from telegram.update import Update from telegram.update import Update
from entity.group import Group
from exception.invalidArgumentException import InvalidArgumentException
@dataclass @dataclass
@ -13,9 +18,17 @@ class UpdateData():
username: str username: str
@staticmethod @staticmethod
def create_from_update(update: Update) -> UpdateData: def create_from_arguments(update: Update, context: CallbackContext) -> UpdateData:
user_id = str(update.effective_user.id)
chat_id = str(update.effective_chat.id) chat_id = str(update.effective_chat.id)
if context.args and context.args[0]:
if not context.args[0].isalpha() or context.args[0] == Group.default_name:
raise InvalidArgumentException(re.escape(f'Group name must contain only letters and can not be `{Group.default_name}`.'))
else:
chat_id += f'~{context.args[0]}'.lower()
user_id = str(update.effective_user.id)
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

@ -0,0 +1,53 @@
import itertools
import re
from typing import Iterable
from database.client import Client
from entity.group import Group
from entity.user import User
class GroupRepository():
client: Client
count: str = 'count'
def __init__(self) -> None:
self.client = Client()
def get_by_chat_id(self, chat_id: str) -> Iterable[Group]:
groups = self.client.aggregate(
User.collection,
[
{ "$unwind": f'${User.chats_index}' },
{
"$match": {
User.chats_index: { "$regex": re.compile(f'^{chat_id}.*$') },
},
},
{
"$group": {
"_id": {
"$last": { "$split": [f'${User.chats_index}', "~"] },
},
self.count: { "$count": {} },
},
},
{
"$sort": { '_id': 1 }
}
]
)
result = []
for group in groups:
group_name = group['_id']
if group_name == chat_id:
group_name = Group.default_name
result.append(
Group(chat_id, group_name, group[self.count])
)
return result

View File

@ -2,3 +2,4 @@ python-dotenv==0.19.0
python-telegram-bot==13.7 python-telegram-bot==13.7
pymongo==3.12.0 pymongo==3.12.0
names==0.3.0 names==0.3.0
prettytable==2.2.1