mirror of
https://github.com/miloszowi/everyone-mention-telegram-bot.git
synced 2025-05-20 09:14:07 +00:00
changed mongoDb collection structure, removed group entity & repository, updated README.md, changed folder names to singular forms
This commit is contained in:
parent
baa8a78cad
commit
cd8e3507ca
60
README.md
60
README.md
@ -3,53 +3,61 @@
|
|||||||
<p align="center"> simple, but useful telegram bot to gather all of group members attention!
|
<p align="center"> simple, but useful telegram bot to gather all of group members attention!
|
||||||
<!-- Icon made by https://www.freepik.com from https://www.flaticon.com/ -->
|
<!-- Icon made by https://www.freepik.com from https://www.flaticon.com/ -->
|
||||||
|
|
||||||
## Contents
|
# Contents
|
||||||
|
|
||||||
* [Getting started.](#getting-started)
|
* [Getting started.](#getting-started)
|
||||||
* [Installation](#installation)
|
|
||||||
* [Requirements](#requirements)
|
* [Requirements](#requirements)
|
||||||
* [Env file](#env-file)
|
* [Installation](#installation)
|
||||||
|
* [Logs](#logs)
|
||||||
|
* [Env files](#env-files)
|
||||||
* [Commands](#commands)
|
* [Commands](#commands)
|
||||||
* [`/in`](#in)
|
* [`/in`](#in)
|
||||||
* [`/out`](#out)
|
* [`/out`](#out)
|
||||||
* [`/everyone`](#everyone)
|
* [`/everyone`](#everyone)
|
||||||
|
|
||||||
### Getting started
|
## Getting started
|
||||||
#### Installation
|
|
||||||
|
### Requirements
|
||||||
|
- `docker-compose` in version `1.25.0`
|
||||||
|
- `docker` in version `20.10.7`
|
||||||
|
|
||||||
|
### Installation
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/miloszowi/everyone-mention-telegram-bot.git
|
git clone https://github.com/miloszowi/everyone-mention-telegram-bot.git
|
||||||
pip install -r requirements.txt
|
|
||||||
python entrypoint.py
|
|
||||||
```
|
```
|
||||||
|
after that, you need to copy env files and fulfill it with correct values
|
||||||
#### Requirements
|
|
||||||
- `python` with version specified in `runtime.txt`
|
|
||||||
- `pip` with version `20.0.2`
|
|
||||||
|
|
||||||
#### Env files
|
|
||||||
First, copy env files for database and app containers
|
|
||||||
```bash
|
```bash
|
||||||
cp docker/config/app/app.dist.env docker/config/app/app.env
|
cp docker/config/app.dist.env docker/config/app.env
|
||||||
cp docker/config/database/database.dist.env docker/config/app/app.env
|
cp docker/config/database.dist.env docker/config/app.env
|
||||||
```
|
```
|
||||||
and then fulfill copied `.env` files with required values
|
and finally, you can run the bot by launching docker containers
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
(`-d` flag will run containers in detached mode)
|
||||||
|
### Logs
|
||||||
|
You can use
|
||||||
|
```bash
|
||||||
|
docker/logs <container>
|
||||||
|
```
|
||||||
|
to check container logs
|
||||||
|
### Env files
|
||||||
app.env
|
app.env
|
||||||
- `bot_token` - your telegram bot token from [BotFather](https://telegram.me/BotFather)
|
- `BOT_TOKEN` - your telegram bot token from [BotFather](https://telegram.me/BotFather)
|
||||||
- `MONGODB_DATABASE` - MongoDB database name
|
- `MONGODB_DATABASE` - MongoDB database name
|
||||||
- `MONGODB_USERNAME` - MongoDB username
|
- `MONGODB_USERNAME` - MongoDB username
|
||||||
- `MONGODB_PASSWORD` - MongoDB password
|
- `MONGODB_PASSWORD` - MongoDB password
|
||||||
- `MONGODB_HOSTNAME` - MongoDB host (default `database` - container name)
|
- `MONGODB_HOSTNAME` - MongoDB host (default `database` - container name)
|
||||||
- `MONGODB_PORT` - MongoDB port (default `port` - given in docker-compose configuration)
|
- `MONGODB_PORT` - MongoDB port (default `27017` - given in docker-compose configuration)
|
||||||
|
|
||||||
database.env
|
database.env
|
||||||
- `MONGO_INITDB_ROOT_USERNAME` - conf from `app.env`
|
- `MONGO_INITDB_ROOT_USERNAME` - conf from `app.env`
|
||||||
- `MONGO_INITDB_ROOT_PASSWORD` - conf from `app.env`
|
- `MONGO_INITDB_ROOT_PASSWORD` - conf from `app.env`
|
||||||
- `MONGO_INITDB_DATABASE` - conf from `app.env`
|
- `MONGO_INITDB_DATABASE` - conf from `app.env`
|
||||||
- `MONGODB_DATA_DIR` - directory to store MongoDB documents (inside a container)
|
- `MONGODB_DATA_DIR` - directory to store MongoDB documents (inside a container)
|
||||||
- `MONDODB_LOG_DIR` - log file
|
- `MONDODB_LOG_DIR` - path to logs storage
|
||||||
### Commands
|
## Commands
|
||||||
#### `/in`
|
### `/in`
|
||||||
Will sign you in for everyone-mentions.
|
Will sign you in for everyone-mentions.
|
||||||
|
|
||||||

|

|
||||||
@ -58,7 +66,7 @@ If you have already opted-in before, alternative reply will be displayed.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### `/out`
|
### `/out`
|
||||||
Will sign you off for everyone-mentions.
|
Will sign you off for everyone-mentions.
|
||||||
|
|
||||||

|

|
||||||
@ -67,10 +75,10 @@ If you haven't opted-in before, alternative reply will be displayed.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### `/everone`
|
### `/everyone`
|
||||||
Will mention everyone that opted-in for everyone-mentions separated by spaces.
|
Will mention everyone that opted-in for everyone-mentions separated by spaces.
|
||||||
|
|
||||||
If user does not have nickname, it will assign random name from `names` python library to his ID
|
If user does not have nickname, it will first try to assign his firstname, then random firstname from `names` python library
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ services:
|
|||||||
image: mongo:4.0.8
|
image: mongo:4.0.8
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
- ./docker/config/database/database.env
|
- ./docker/config/database.env
|
||||||
volumes:
|
volumes:
|
||||||
- db-data:/data/db
|
- db-data:/data/db
|
||||||
ports:
|
ports:
|
||||||
@ -18,7 +18,7 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
command: python app.py
|
command: python app.py
|
||||||
env_file:
|
env_file:
|
||||||
- ./docker/config/app/app.env
|
- ./docker/config/app.env
|
||||||
volumes:
|
volumes:
|
||||||
- ./src:/src
|
- ./src:/src
|
||||||
ports:
|
ports:
|
||||||
|
23
src/app.py
23
src/app.py
@ -1,15 +1,17 @@
|
|||||||
from config.credentials import bot_token
|
|
||||||
from config.handlers import handlers
|
|
||||||
from handlers.handlerInterface import HandlerInterface
|
|
||||||
from telegram.ext.dispatcher import Dispatcher
|
|
||||||
from telegram.ext import Updater
|
from telegram.ext import Updater
|
||||||
|
from telegram.ext.dispatcher import Dispatcher
|
||||||
|
|
||||||
|
from config.credentials import BOT_TOKEN
|
||||||
|
from handler.abstractHandler import AbstractHandler
|
||||||
|
from handler import (inHandler, mentionHandler, outHandler)
|
||||||
|
|
||||||
|
|
||||||
class App:
|
class App:
|
||||||
updater: Updater
|
updater: Updater
|
||||||
dispatcher: Dispatcher
|
dispatcher: Dispatcher
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.updater = Updater(bot_token)
|
self.updater = Updater(BOT_TOKEN)
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
self.registerHandlers()
|
self.registerHandlers()
|
||||||
@ -18,14 +20,13 @@ class App:
|
|||||||
self.updater.idle()
|
self.updater.idle()
|
||||||
|
|
||||||
def registerHandlers(self) -> None:
|
def registerHandlers(self) -> None:
|
||||||
for handler in handlers:
|
for handler in AbstractHandler.__subclasses__():
|
||||||
if not isinstance(handler, HandlerInterface):
|
self.updater.dispatcher.add_handler(
|
||||||
raise Exception('Invalid list of handlers provided. Handler must implement HandlerInterface')
|
handler().getBotHandler()
|
||||||
|
)
|
||||||
self.updater.dispatcher.add_handler(handler.getBotHandler())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = App()
|
app = App()
|
||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
# These are MarkdownV2 python-telegram-bot specific
|
# These are MarkdownV2 python-telegram-bot specific
|
||||||
opted_in_successfully = re.escape('You have opted-in for everyone-mentions.')
|
opted_in = re.escape('You have opted-in for everyone-mentions.')
|
||||||
opted_in_failed = re.escape('You already opted-in for everyone-mentions.')
|
opted_in_failed = re.escape('You already opted-in for everyone-mentions.')
|
||||||
opted_off_successfully = 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.')
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
bot_token = os.environ['bot_token']
|
BOT_TOKEN = os.environ['BOT_TOKEN']
|
||||||
|
|
||||||
MONGODB_DATABASE=os.environ['MONGODB_DATABASE']
|
MONGODB_DATABASE=os.environ['MONGODB_DATABASE']
|
||||||
MONGODB_USERNAME=os.environ['MONGODB_USERNAME']
|
MONGODB_USERNAME=os.environ['MONGODB_USERNAME']
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
from handlers.inHandler import InHandler
|
|
||||||
from handlers.outHandler import OutHandler
|
|
||||||
from handlers.mentionHandler import MentionHandler
|
|
||||||
|
|
||||||
handlers = [
|
|
||||||
InHandler(),
|
|
||||||
OutHandler(),
|
|
||||||
MentionHandler()
|
|
||||||
]
|
|
@ -1,10 +1,13 @@
|
|||||||
from pymongo.errors import ServerSelectionTimeoutError
|
|
||||||
from config.credentials import MONGODB_USERNAME, MONGODB_PASSWORD, MONGODB_DATABASE, MONGODB_HOSTNAME, MONGODB_PORT
|
|
||||||
from pymongo import MongoClient
|
|
||||||
from pymongo.database import Database
|
|
||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
class DatabaseClient():
|
from config.credentials import (MONGODB_DATABASE, MONGODB_HOSTNAME,
|
||||||
|
MONGODB_PASSWORD, MONGODB_PORT,
|
||||||
|
MONGODB_USERNAME)
|
||||||
|
from pymongo import MongoClient
|
||||||
|
from pymongo.database import Database
|
||||||
|
|
||||||
|
|
||||||
|
class Client():
|
||||||
mongoClient: MongoClient
|
mongoClient: MongoClient
|
||||||
database: Database
|
database: Database
|
||||||
|
|
||||||
@ -17,14 +20,17 @@ class DatabaseClient():
|
|||||||
self.mongoClient = MongoClient(uri)
|
self.mongoClient = MongoClient(uri)
|
||||||
self.database = self.mongoClient[MONGODB_DATABASE]
|
self.database = self.mongoClient[MONGODB_DATABASE]
|
||||||
|
|
||||||
def insert(self, collection: str, data: dict) -> None:
|
def insertOne(self, collection: str, data: dict) -> None:
|
||||||
self.database.get_collection(collection).insert_one(data)
|
self.database.get_collection(collection).insert_one(data)
|
||||||
|
|
||||||
def find(self, collection: str, query: dict) -> dict:
|
|
||||||
return self.database.get_collection(collection).find(query)
|
|
||||||
|
|
||||||
def findOne(self, collection: str, query: dict) -> dict:
|
def findOne(self, collection: str, query: dict) -> dict:
|
||||||
return self.database.get_collection(collection).find_one(query)
|
return self.database.get_collection(collection).find_one(query)
|
||||||
|
|
||||||
def remove(self, collection: str, data: dict) -> None:
|
def findMany(self, collection: str, filter: dict) -> dict:
|
||||||
self.database.get_collection(collection).remove(data)
|
return self.database.get_collection(collection).find(filter)
|
||||||
|
|
||||||
|
def updateOne(self, collection: str, filter: dict, data: dict) -> None:
|
||||||
|
self.database.get_collection(collection).update_one(
|
||||||
|
filter,
|
||||||
|
{ "$set" : data }
|
||||||
|
)
|
@ -1,28 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
class Chat():
|
|
||||||
id: str
|
|
||||||
|
|
||||||
def __init__(self, id: str) -> None:
|
|
||||||
self.id = id
|
|
||||||
|
|
||||||
def getId(self) -> str:
|
|
||||||
return self.id
|
|
||||||
|
|
||||||
def toDict(self) -> dict:
|
|
||||||
return {
|
|
||||||
'_id': self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getMongoRoot() -> str:
|
|
||||||
return 'chat'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def fromDocument(document: Optional[dict]) -> Optional[Chat]:
|
|
||||||
if not document:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return Chat(document['_id'])
|
|
@ -1,34 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
class ChatPerson():
|
|
||||||
chat_id: str
|
|
||||||
person_id: str
|
|
||||||
|
|
||||||
def __init__(self, chatId: str, personId: str) -> None:
|
|
||||||
self.chat_id = chatId
|
|
||||||
self.person_id = personId
|
|
||||||
|
|
||||||
def getChatId(self) -> str:
|
|
||||||
return self.chat_id
|
|
||||||
|
|
||||||
def getPersonId(self) -> str:
|
|
||||||
return self.person_id
|
|
||||||
|
|
||||||
def toDict(self) -> dict:
|
|
||||||
return {
|
|
||||||
'_id': f'{self.chat_id}-{self.person_id}',
|
|
||||||
'chat_id': self.chat_id,
|
|
||||||
'person_id': self.person_id
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getMongoRoot() -> str:
|
|
||||||
return 'chat_person'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def fromDocument(document: Optional[dict]) -> Optional[ChatPerson]:
|
|
||||||
if not document:
|
|
||||||
return None
|
|
||||||
return ChatPerson(document['chat_id'], document['person_id'])
|
|
@ -1,44 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
from abc import abstractmethod
|
|
||||||
from typing import Optional
|
|
||||||
import names
|
|
||||||
|
|
||||||
|
|
||||||
class Person():
|
|
||||||
id: str
|
|
||||||
username: str
|
|
||||||
|
|
||||||
def __init__(self, id: str, username: Optional[str] = None) -> None:
|
|
||||||
self.id = id
|
|
||||||
|
|
||||||
if not username:
|
|
||||||
self.username = names.get_first_name()
|
|
||||||
else:
|
|
||||||
self.username = username
|
|
||||||
|
|
||||||
def getId(self) -> str:
|
|
||||||
return self.id
|
|
||||||
|
|
||||||
def getUsername(self) -> str:
|
|
||||||
return self.username
|
|
||||||
|
|
||||||
def toDict(self, withUsername: bool = True) -> dict:
|
|
||||||
result = {
|
|
||||||
'_id': self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
if withUsername:
|
|
||||||
result['username'] = self.username
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getMongoRoot() -> str:
|
|
||||||
return 'person'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def fromDocument(document: Optional[dict]) -> Optional[Person]:
|
|
||||||
if not document:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return Person(document['_id'], document['username'])
|
|
52
src/entity/user.py
Normal file
52
src/entity/user.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
|
||||||
|
class User():
|
||||||
|
collection: str = 'users'
|
||||||
|
idIndex: str = '_id'
|
||||||
|
chatsIndex: str = 'chats'
|
||||||
|
usernameIndex: str = 'username'
|
||||||
|
|
||||||
|
userId: str
|
||||||
|
username: str
|
||||||
|
chats: Iterable[str]
|
||||||
|
|
||||||
|
def __init__(self, userId, username, chats) -> None:
|
||||||
|
self.userId = userId
|
||||||
|
self.username = username
|
||||||
|
self.chats = chats
|
||||||
|
|
||||||
|
def getUserId(self) -> str:
|
||||||
|
return self.userId
|
||||||
|
|
||||||
|
def getUsername(self) -> str:
|
||||||
|
return self.username
|
||||||
|
|
||||||
|
def getChats(self) -> Iterable[str]:
|
||||||
|
return self.chats
|
||||||
|
|
||||||
|
def isInChat(self, chatId: str) -> bool:
|
||||||
|
return chatId in self.getChats()
|
||||||
|
|
||||||
|
def addToChat(self, chatId: str) -> None:
|
||||||
|
self.chats.append(chatId)
|
||||||
|
|
||||||
|
def removeFromChat(self, chatId: str) -> None:
|
||||||
|
if chatId in self.getChats():
|
||||||
|
self.chats.remove(chatId)
|
||||||
|
|
||||||
|
def toMongoDocument(self) -> dict:
|
||||||
|
return {
|
||||||
|
self.usernameIndex: self.getUsername(),
|
||||||
|
self.chatsIndex: self.getChats()
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fromMongoDocument(mongoDocument: dict) -> User:
|
||||||
|
return User(
|
||||||
|
mongoDocument[User.idIndex],
|
||||||
|
mongoDocument[User.usernameIndex],
|
||||||
|
mongoDocument[User.chatsIndex]
|
||||||
|
)
|
2
src/exception/alreadyExistsException.py
Normal file
2
src/exception/alreadyExistsException.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
class AlreadyExistsException(Exception):
|
||||||
|
pass
|
2
src/exception/notFoundException.py
Normal file
2
src/exception/notFoundException.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
class NotFoundException(Exception):
|
||||||
|
pass
|
@ -1,10 +1,13 @@
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
|
||||||
from telegram.ext.callbackcontext import CallbackContext
|
from telegram.ext.callbackcontext import CallbackContext
|
||||||
from telegram.ext.handler import Handler
|
from telegram.ext.handler import Handler
|
||||||
from telegram.update import Update
|
from telegram.update import Update
|
||||||
|
|
||||||
|
from handler.vo.updateData import UpdateData
|
||||||
|
|
||||||
class HandlerInterface:
|
|
||||||
|
class AbstractHandler:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -14,8 +17,8 @@ class HandlerInterface:
|
|||||||
@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')
|
||||||
|
|
||||||
@abstractmethod
|
def getUpdateData(self, update: Update) -> UpdateData:
|
||||||
def getCommandName(self) -> str: raise Exception('getCommandName method is not implemented')
|
return UpdateData.createFromUpdate(update)
|
||||||
|
|
||||||
def reply(self, update: Update, message: str) -> None:
|
def reply(self, update: Update, message: str) -> None:
|
||||||
update.effective_message.reply_markdown_v2(text=message)
|
update.effective_message.reply_markdown_v2(text=message)
|
38
src/handler/inHandler.py
Executable file
38
src/handler/inHandler.py
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
from config.contents import opted_in, opted_in_failed
|
||||||
|
from exception.notFoundException import NotFoundException
|
||||||
|
from repository.userRepository import UserRepository
|
||||||
|
from telegram.ext.callbackcontext import CallbackContext
|
||||||
|
from telegram.ext.commandhandler import CommandHandler
|
||||||
|
from telegram.update import Update
|
||||||
|
|
||||||
|
from handler.abstractHandler import AbstractHandler
|
||||||
|
|
||||||
|
|
||||||
|
class InHandler(AbstractHandler):
|
||||||
|
botHandler: CommandHandler
|
||||||
|
userRepository: UserRepository
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.botHandler = CommandHandler('in', self.handle)
|
||||||
|
self.userRepository = UserRepository()
|
||||||
|
|
||||||
|
def handle(self, update: Update, context: CallbackContext) -> None:
|
||||||
|
updateData = self.getUpdateData(update)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = self.userRepository.getById(updateData.getUserId())
|
||||||
|
|
||||||
|
if user.isInChat(updateData.getChatId()):
|
||||||
|
self.reply(update, opted_in_failed)
|
||||||
|
return
|
||||||
|
|
||||||
|
user.addToChat(updateData.getChatId())
|
||||||
|
self.userRepository.save(user)
|
||||||
|
|
||||||
|
except NotFoundException:
|
||||||
|
self.userRepository.saveByUpdateData(updateData)
|
||||||
|
|
||||||
|
self.reply(update, opted_in)
|
||||||
|
|
||||||
|
def getBotHandler(self) -> CommandHandler:
|
||||||
|
return self.botHandler
|
40
src/handler/mentionHandler.py
Executable file
40
src/handler/mentionHandler.py
Executable file
@ -0,0 +1,40 @@
|
|||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from config.contents import mention_failed
|
||||||
|
from entity.user import User
|
||||||
|
from repository.userRepository import UserRepository
|
||||||
|
from telegram.ext.callbackcontext import CallbackContext
|
||||||
|
from telegram.ext.commandhandler import CommandHandler
|
||||||
|
from telegram.update import Update
|
||||||
|
|
||||||
|
from handler.abstractHandler import AbstractHandler
|
||||||
|
|
||||||
|
|
||||||
|
class MentionHandler(AbstractHandler):
|
||||||
|
botHandler: CommandHandler
|
||||||
|
userRepository: UserRepository
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.botHandler = CommandHandler('everyone', self.handle)
|
||||||
|
self.userRepository = UserRepository()
|
||||||
|
|
||||||
|
def handle(self, update: Update, context: CallbackContext) -> None:
|
||||||
|
updateData = self.getUpdateData(update)
|
||||||
|
users = self.userRepository.getAllForChat(updateData.getChatId())
|
||||||
|
|
||||||
|
if users:
|
||||||
|
self.reply(update, self.buildMentionMessage(users))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.reply(update, mention_failed)
|
||||||
|
|
||||||
|
def getBotHandler(self) -> CommandHandler:
|
||||||
|
return self.botHandler
|
||||||
|
|
||||||
|
def buildMentionMessage(self, users: Iterable[User]) -> str:
|
||||||
|
result = ''
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
result += f'*[{user.getUsername()}](tg://user?id={user.getUserId()})* '
|
||||||
|
|
||||||
|
return result
|
36
src/handler/outHandler.py
Executable file
36
src/handler/outHandler.py
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
from config.contents import opted_off, opted_off_failed
|
||||||
|
from exception.notFoundException import NotFoundException
|
||||||
|
from repository.userRepository import UserRepository
|
||||||
|
from telegram.ext.callbackcontext import CallbackContext
|
||||||
|
from telegram.ext.commandhandler import CommandHandler
|
||||||
|
from telegram.update import Update
|
||||||
|
|
||||||
|
from handler.abstractHandler import AbstractHandler
|
||||||
|
|
||||||
|
|
||||||
|
class OutHandler(AbstractHandler):
|
||||||
|
botHandler: CommandHandler
|
||||||
|
userRepository: UserRepository
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.botHandler = CommandHandler('out', self.handle)
|
||||||
|
self.userRepository = UserRepository()
|
||||||
|
|
||||||
|
def handle(self, update: Update, context: CallbackContext) -> None:
|
||||||
|
updateData = self.getUpdateData(update)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = self.userRepository.getById(updateData.getUserId())
|
||||||
|
if not user.isInChat(updateData.getChatId()):
|
||||||
|
raise NotFoundException()
|
||||||
|
except NotFoundException:
|
||||||
|
self.reply(update, opted_off_failed)
|
||||||
|
return
|
||||||
|
|
||||||
|
user.removeFromChat(updateData.getChatId())
|
||||||
|
self.userRepository.save(user)
|
||||||
|
|
||||||
|
self.reply(update, opted_off)
|
||||||
|
|
||||||
|
def getBotHandler(self) -> CommandHandler:
|
||||||
|
return self.botHandler
|
36
src/handler/vo/updateData.py
Normal file
36
src/handler/vo/updateData.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import names
|
||||||
|
from telegram.update import Update
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateData():
|
||||||
|
userId: str
|
||||||
|
chatId: str
|
||||||
|
username: str
|
||||||
|
|
||||||
|
def __init__(self, userId: str, chatId: str, username: str) -> None:
|
||||||
|
self.userId = userId
|
||||||
|
self.chatId = chatId
|
||||||
|
self.username = username
|
||||||
|
|
||||||
|
def getUserId(self) -> str:
|
||||||
|
return self.userId
|
||||||
|
|
||||||
|
def getChatId(self) -> str:
|
||||||
|
return self.chatId
|
||||||
|
|
||||||
|
def getUsername(self) -> str:
|
||||||
|
return self.username
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def createFromUpdate(update: Update) -> UpdateData:
|
||||||
|
userId = str(update.effective_user.id)
|
||||||
|
chatId = str(update.effective_chat.id)
|
||||||
|
chatId = "-284685928"
|
||||||
|
username = update.effective_user.username or update.effective_user.first_name
|
||||||
|
|
||||||
|
if not username:
|
||||||
|
username = names.get_first_name()
|
||||||
|
|
||||||
|
return UpdateData(userId, chatId, username)
|
@ -1,38 +0,0 @@
|
|||||||
from config.contents import opted_in_successfully, opted_in_failed
|
|
||||||
from repositories.relationRepository import RelationRepository
|
|
||||||
from database.databaseClient import DatabaseClient
|
|
||||||
from handlers.handlerInterface import HandlerInterface
|
|
||||||
from telegram.ext.callbackcontext import CallbackContext
|
|
||||||
from telegram.ext.commandhandler import CommandHandler
|
|
||||||
from telegram.update import Update
|
|
||||||
|
|
||||||
class InHandler(HandlerInterface):
|
|
||||||
botHandler: CommandHandler
|
|
||||||
commandName: str = 'in'
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.botHandler = CommandHandler(
|
|
||||||
self.getCommandName(),
|
|
||||||
self.handle
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, update: Update, context: CallbackContext) -> None:
|
|
||||||
personId = update.effective_user.id
|
|
||||||
chatId = update.effective_chat.id
|
|
||||||
username = update.effective_user.username
|
|
||||||
|
|
||||||
relationRepository = RelationRepository()
|
|
||||||
relation = relationRepository.get(chatId, personId)
|
|
||||||
|
|
||||||
if relation:
|
|
||||||
self.reply(update, opted_in_failed)
|
|
||||||
return
|
|
||||||
|
|
||||||
relationRepository.save(chatId, personId, username)
|
|
||||||
self.reply(update, opted_in_successfully)
|
|
||||||
|
|
||||||
def getBotHandler(self) -> CommandHandler:
|
|
||||||
return self.botHandler
|
|
||||||
|
|
||||||
def getCommandName(self) -> str:
|
|
||||||
return self.commandName
|
|
@ -1,45 +0,0 @@
|
|||||||
from typing import Iterable
|
|
||||||
from config.contents import mention_failed
|
|
||||||
from entities.person import Person
|
|
||||||
from handlers.handlerInterface import HandlerInterface
|
|
||||||
from repositories.relationRepository import RelationRepository
|
|
||||||
from repositories.personRepository import PersonRepository
|
|
||||||
from telegram.ext.callbackcontext import CallbackContext
|
|
||||||
from telegram.ext.commandhandler import CommandHandler
|
|
||||||
from telegram.update import Update
|
|
||||||
|
|
||||||
|
|
||||||
class MentionHandler(HandlerInterface):
|
|
||||||
botHandler: CommandHandler
|
|
||||||
commandName: str = 'everyone'
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.botHandler = CommandHandler(
|
|
||||||
self.getCommandName(),
|
|
||||||
self.handle
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, update: Update, context: CallbackContext) -> None:
|
|
||||||
relationRepository = RelationRepository()
|
|
||||||
persons = relationRepository.getPersonsForChat(update.effective_chat.id)
|
|
||||||
|
|
||||||
if not persons:
|
|
||||||
self.reply(update, mention_failed)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.reply(update, self.buildMentionMessage(persons))
|
|
||||||
|
|
||||||
|
|
||||||
def getBotHandler(self) -> CommandHandler:
|
|
||||||
return self.botHandler
|
|
||||||
|
|
||||||
def getCommandName(self) -> str:
|
|
||||||
return self.commandName
|
|
||||||
|
|
||||||
def buildMentionMessage(self, persons: Iterable[Person]) -> str:
|
|
||||||
result = ''
|
|
||||||
|
|
||||||
for person in persons:
|
|
||||||
result += f'*[{person.getUsername()}](tg://user?id={person.getId()})* '
|
|
||||||
|
|
||||||
return result
|
|
@ -1,38 +0,0 @@
|
|||||||
from config.contents import opted_off_successfully, opted_off_failed
|
|
||||||
from handlers.handlerInterface import HandlerInterface
|
|
||||||
from telegram.ext.callbackcontext import CallbackContext
|
|
||||||
from telegram.ext.commandhandler import CommandHandler
|
|
||||||
from telegram.update import Update
|
|
||||||
|
|
||||||
from repositories.relationRepository import RelationRepository
|
|
||||||
|
|
||||||
|
|
||||||
class OutHandler(HandlerInterface):
|
|
||||||
botHandler: CommandHandler
|
|
||||||
commandName: str = 'out'
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.botHandler = CommandHandler(
|
|
||||||
self.getCommandName(),
|
|
||||||
self.handle
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, update: Update, context: CallbackContext) -> None:
|
|
||||||
personId = update.effective_user.id
|
|
||||||
chatId = update.effective_chat.id
|
|
||||||
|
|
||||||
relationRepository = RelationRepository()
|
|
||||||
relation = relationRepository.get(chatId, personId)
|
|
||||||
|
|
||||||
if not relation:
|
|
||||||
self.reply(update, opted_off_failed)
|
|
||||||
return
|
|
||||||
|
|
||||||
relationRepository.remove(relation)
|
|
||||||
self.reply(update, opted_off_successfully)
|
|
||||||
|
|
||||||
def getBotHandler(self) -> CommandHandler:
|
|
||||||
return self.botHandler
|
|
||||||
|
|
||||||
def getCommandName(self) -> str:
|
|
||||||
return self.commandName
|
|
@ -1,18 +0,0 @@
|
|||||||
from database.databaseClient import DatabaseClient
|
|
||||||
from entities.chat import Chat
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
class ChatRepository:
|
|
||||||
database: DatabaseClient
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.database = DatabaseClient()
|
|
||||||
|
|
||||||
def get(self, id: str) -> Optional[Chat]:
|
|
||||||
chat = Chat(id)
|
|
||||||
search = self.database.findOne(Chat.getMongoRoot(), chat.toDict())
|
|
||||||
|
|
||||||
return Chat.fromDocument(search)
|
|
||||||
|
|
||||||
def save(self, chat: Chat) -> None:
|
|
||||||
self.database.insert(Chat.getMongoRoot(), chat.toDict())
|
|
@ -1,27 +0,0 @@
|
|||||||
from database.databaseClient import DatabaseClient
|
|
||||||
from entities.person import Person
|
|
||||||
from typing import Iterable, Optional
|
|
||||||
|
|
||||||
class PersonRepository:
|
|
||||||
database: DatabaseClient
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.database = DatabaseClient()
|
|
||||||
|
|
||||||
def get(self, id: str) -> Optional[Person]:
|
|
||||||
person = Person(id)
|
|
||||||
search = self.database.findOne(Person.getMongoRoot(), person.toDict(False))
|
|
||||||
|
|
||||||
return Person.fromDocument(search)
|
|
||||||
|
|
||||||
def find(self, query: dict) -> Iterable[Person]:
|
|
||||||
result = []
|
|
||||||
search = self.database.find(Person.getMongoRoot(), query)
|
|
||||||
|
|
||||||
for document in search:
|
|
||||||
result.append(Person.fromDocument(document))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def save(self, person: Person) -> None:
|
|
||||||
self.database.insert(Person.getMongoRoot(), person.toDict())
|
|
@ -1,56 +0,0 @@
|
|||||||
from typing import Iterable, Optional
|
|
||||||
from database.databaseClient import DatabaseClient
|
|
||||||
from entities.chat import Chat
|
|
||||||
from entities.chatPerson import ChatPerson
|
|
||||||
from entities.person import Person
|
|
||||||
from repositories.personRepository import PersonRepository
|
|
||||||
from repositories.chatRepository import ChatRepository
|
|
||||||
|
|
||||||
|
|
||||||
class RelationRepository():
|
|
||||||
client: DatabaseClient
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.client = DatabaseClient()
|
|
||||||
|
|
||||||
def get(self, chatId: str, personId: str) -> Optional[ChatPerson]:
|
|
||||||
relation = ChatPerson(chatId, personId)
|
|
||||||
search = self.client.findOne(ChatPerson.getMongoRoot(), relation.toDict())
|
|
||||||
|
|
||||||
return ChatPerson.fromDocument(search)
|
|
||||||
|
|
||||||
def save(self, chatId: str, personId: str, username: Optional[str] = None) -> None:
|
|
||||||
relation = ChatPerson(chatId, personId)
|
|
||||||
|
|
||||||
self.client.insert(ChatPerson.getMongoRoot(), relation.toDict())
|
|
||||||
personRepository = PersonRepository()
|
|
||||||
person = personRepository.get(personId)
|
|
||||||
|
|
||||||
if not person:
|
|
||||||
person = Person(personId, username)
|
|
||||||
personRepository.save(person)
|
|
||||||
|
|
||||||
chatRepository = ChatRepository()
|
|
||||||
chat = chatRepository.get(chatId)
|
|
||||||
|
|
||||||
if not chat:
|
|
||||||
chat = Chat(chatId)
|
|
||||||
chatRepository.save(chat)
|
|
||||||
|
|
||||||
def getPersonsForChat(self, chatId: str) -> Iterable[ChatPerson]:
|
|
||||||
result = []
|
|
||||||
relations = self.client.find(ChatPerson.getMongoRoot(), {'chat_id': chatId})
|
|
||||||
|
|
||||||
search = {}
|
|
||||||
for relation in relations:
|
|
||||||
search['_id'] = relation['person_id']
|
|
||||||
|
|
||||||
if not search:
|
|
||||||
return result
|
|
||||||
|
|
||||||
personRepository = PersonRepository()
|
|
||||||
return personRepository.find(search)
|
|
||||||
|
|
||||||
def remove(self, relation: ChatPerson) -> None:
|
|
||||||
self.client.remove(ChatPerson.getMongoRoot() ,relation.toDict())
|
|
||||||
|
|
66
src/repository/userRepository.py
Normal file
66
src/repository/userRepository.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from typing import Iterable, Optional
|
||||||
|
|
||||||
|
from database.client import Client
|
||||||
|
from entity.user import User
|
||||||
|
from exception.notFoundException import NotFoundException
|
||||||
|
from handler.vo.updateData import UpdateData
|
||||||
|
|
||||||
|
|
||||||
|
class UserRepository():
|
||||||
|
client: Client
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
def getById(self, id: str) -> User:
|
||||||
|
user = self.client.findOne(
|
||||||
|
User.collection,
|
||||||
|
{
|
||||||
|
User.idIndex: id
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise NotFoundException(f'Could not find user with "{id}" id')
|
||||||
|
|
||||||
|
return User(
|
||||||
|
user[User.idIndex],
|
||||||
|
user[User.usernameIndex],
|
||||||
|
user[User.chatsIndex]
|
||||||
|
)
|
||||||
|
|
||||||
|
def save(self, user: User) -> None:
|
||||||
|
self.client.updateOne(
|
||||||
|
User.collection,
|
||||||
|
{ User.idIndex: user.getUserId() },
|
||||||
|
user.toMongoDocument()
|
||||||
|
)
|
||||||
|
|
||||||
|
def saveByUpdateData(self, data: UpdateData) -> None:
|
||||||
|
self.client.insertOne(
|
||||||
|
User.collection,
|
||||||
|
{
|
||||||
|
User.idIndex: data.getUserId(),
|
||||||
|
User.usernameIndex: data.getUsername(),
|
||||||
|
User.chatsIndex: [data.getChatId()]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def getAllForChat(self, chatId: str) -> Iterable[User]:
|
||||||
|
result = []
|
||||||
|
users = self.client.findMany(
|
||||||
|
User.collection,
|
||||||
|
{
|
||||||
|
User.chatsIndex: {
|
||||||
|
"$in" : [chatId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for record in users:
|
||||||
|
result.append(User.fromMongoDocument(record))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user