mirror of
https://github.com/miloszowi/everyone-mention-telegram-bot.git
synced 2025-05-20 17:24:06 +00:00
mongodb to 5.0.2 update, added groups command, added groups logic, changed silent logic
This commit is contained in:
parent
bbb1706dbe
commit
7c355d6cbe
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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.')
|
@ -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
12
src/entity/group.py
Normal 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'
|
@ -1,2 +0,0 @@
|
|||||||
class AlreadyExistsException(Exception):
|
|
||||||
pass
|
|
2
src/exception/invalidArgumentException.py
Normal file
2
src/exception/invalidArgumentException.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
class InvalidArgumentException(Exception):
|
||||||
|
pass
|
@ -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)
|
||||||
|
40
src/handler/groupsHandler.py
Normal file
40
src/handler/groupsHandler.py
Normal 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>'
|
@ -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
|
||||||
|
@ -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
|
|
@ -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
|
||||||
|
21
src/handler/silentMentionHandler.py
Normal file
21
src/handler/silentMentionHandler.py
Normal 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
|
@ -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:
|
||||||
|
53
src/repository/groupRepository.py
Normal file
53
src/repository/groupRepository.py
Normal 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
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user