Firebase to MongoDB change, updated README.md, removed entrypoint.py and heroku-specific files
Author: miloszowi<miloszweb@gmail.com>
@ -1,6 +0,0 @@
|
|||||||
bot_token=
|
|
||||||
firebase_apiKey=
|
|
||||||
firebase_authDomain=
|
|
||||||
firebase_databaseURL=
|
|
||||||
firebase_projectId=
|
|
||||||
firebase_storageBucket=
|
|
2
.gitignore
vendored
Normal file → Executable file
@ -102,6 +102,8 @@ celerybeat.pid
|
|||||||
*.sage.py
|
*.sage.py
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
|
app.env
|
||||||
|
database.env
|
||||||
.env
|
.env
|
||||||
.venv
|
.venv
|
||||||
env/
|
env/
|
||||||
|
7
Dockerfile
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
FROM python:3.8-buster
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
COPY ./src/requirements.txt /src
|
||||||
|
RUN pip install -r ./requirements.txt
|
||||||
|
|
26
README.md
Normal file → Executable file
@ -26,14 +26,28 @@ python entrypoint.py
|
|||||||
- `python` with version specified in `runtime.txt`
|
- `python` with version specified in `runtime.txt`
|
||||||
- `pip` with version `20.0.2`
|
- `pip` with version `20.0.2`
|
||||||
|
|
||||||
#### Env file
|
#### Env files
|
||||||
|
First, copy env files for database and app containers
|
||||||
```bash
|
```bash
|
||||||
cp .env.local .env
|
cp docker/config/app/app.dist.env docker/config/app/app.env
|
||||||
|
cp docker/config/database/database.dist.env docker/config/app/app.env
|
||||||
```
|
```
|
||||||
and then fulfill copied `.env` file with required values
|
and then fulfill copied `.env` files with required values
|
||||||
- `bot_token` - your telegram bot token from [BotFather](https://telegram.me/BotFather)
|
|
||||||
- `firebase_*` - all of those values you can find in firebase console
|
|
||||||
|
|
||||||
|
app.env
|
||||||
|
- `bot_token` - your telegram bot token from [BotFather](https://telegram.me/BotFather)
|
||||||
|
- `MONGODB_DATABASE` - MongoDB database name
|
||||||
|
- `MONGODB_USERNAME` - MongoDB username
|
||||||
|
- `MONGODB_PASSWORD` - MongoDB password
|
||||||
|
- `MONGODB_HOSTNAME` - MongoDB host (default `database` - container name)
|
||||||
|
- `MONGODB_PORT` - MongoDB port (default `port` - given in docker-compose configuration)
|
||||||
|
|
||||||
|
database.env
|
||||||
|
- `MONGO_INITDB_ROOT_USERNAME` - conf from `app.env`
|
||||||
|
- `MONGO_INITDB_ROOT_PASSWORD` - conf from `app.env`
|
||||||
|
- `MONGO_INITDB_DATABASE` - conf from `app.env`
|
||||||
|
- `MONGODB_DATA_DIR` - directory to store MongoDB documents (inside a container)
|
||||||
|
- `MONDODB_LOG_DIR` - log file
|
||||||
### Commands
|
### Commands
|
||||||
#### `/in`
|
#### `/in`
|
||||||
Will sign you in for everyone-mentions.
|
Will sign you in for everyone-mentions.
|
||||||
@ -56,7 +70,7 @@ If you haven't opted-in before, alternative reply will be displayed.
|
|||||||
#### `/everone`
|
#### `/everone`
|
||||||
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 contain nickname, his ID will be present instead of nickname.
|
If user does not have nickname, it will assign random name from `names` python library to his ID
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
37
docker-compose.yml
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
version: "3.6"
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
database:
|
||||||
|
image: mongo:4.0.8
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- ./docker/config/database/database.env
|
||||||
|
volumes:
|
||||||
|
- db-data:/data/db
|
||||||
|
ports:
|
||||||
|
- 27017:27017
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
command: python app.py
|
||||||
|
env_file:
|
||||||
|
- ./docker/config/app/app.env
|
||||||
|
volumes:
|
||||||
|
- ./src:/src
|
||||||
|
ports:
|
||||||
|
- 9000:9000
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
restart: on-failure
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db-data:
|
7
docker/config/app/app.dist.env
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
BOT_TOKEN=
|
||||||
|
|
||||||
|
MONGODB_DATABASE=
|
||||||
|
MONGODB_USERNAME=
|
||||||
|
MONGODB_PASSWORD=
|
||||||
|
MONGODB_HOSTNAME=localhost
|
||||||
|
MONGODB_PORT=27017
|
5
docker/config/database/database.dist.env
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
MONGO_INITDB_ROOT_USERNAME=
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD=
|
||||||
|
MONGO_INITDB_DATABASE=
|
||||||
|
MONGODB_DATA_DIR=/mongo/database
|
||||||
|
MONDODB_LOG_DIR=
|
5
docker/logs
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
[ -z "$1" ] && printf "\nPlease specify service name (ex. app)\n\n" && exit
|
||||||
|
|
||||||
|
docker-compose logs -f "$@"
|
0
docs/commands.png
Normal file → Executable file
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
0
docs/everyone_command.png
Normal file → Executable file
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
0
docs/everyone_noone_to_mention.png
Normal file → Executable file
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
0
docs/in_command.png
Normal file → Executable file
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
0
docs/in_command_already_opted_in.png
Normal file → Executable file
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
0
docs/logo.png
Normal file → Executable file
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
0
docs/out_command.png
Normal file → Executable file
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
0
docs/out_command_did_not_opt_in_before.png
Normal file → Executable file
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
@ -1,6 +0,0 @@
|
|||||||
from src.app import App
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app = App()
|
|
||||||
|
|
||||||
app.run()
|
|
@ -1 +0,0 @@
|
|||||||
python-3.8.10
|
|
14
src/app.py
Normal file → Executable file
@ -1,10 +1,9 @@
|
|||||||
from .config.credentials import bot_token
|
from config.credentials import bot_token
|
||||||
from .config.handlers import handlers
|
from config.handlers import handlers
|
||||||
from .handlers.handlerInterface import HandlerInterface
|
from handlers.handlerInterface import HandlerInterface
|
||||||
from telegram.ext.dispatcher import Dispatcher
|
from telegram.ext.dispatcher import Dispatcher
|
||||||
from telegram.ext import Updater
|
from telegram.ext import Updater
|
||||||
|
|
||||||
|
|
||||||
class App:
|
class App:
|
||||||
updater: Updater
|
updater: Updater
|
||||||
dispatcher: Dispatcher
|
dispatcher: Dispatcher
|
||||||
@ -14,6 +13,7 @@ class App:
|
|||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
self.registerHandlers()
|
self.registerHandlers()
|
||||||
|
|
||||||
self.updater.start_polling()
|
self.updater.start_polling()
|
||||||
self.updater.idle()
|
self.updater.idle()
|
||||||
|
|
||||||
@ -23,3 +23,9 @@ class App:
|
|||||||
raise Exception('Invalid list of handlers provided. Handler must implement HandlerInterface')
|
raise Exception('Invalid list of handlers provided. Handler must implement HandlerInterface')
|
||||||
|
|
||||||
self.updater.dispatcher.add_handler(handler.getBotHandler())
|
self.updater.dispatcher.add_handler(handler.getBotHandler())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = App()
|
||||||
|
|
||||||
|
app.run()
|
0
src/config/contents.py
Normal file → Executable file
12
src/config/credentials.py
Normal file → Executable file
@ -5,10 +5,8 @@ load_dotenv()
|
|||||||
|
|
||||||
bot_token = os.environ['bot_token']
|
bot_token = os.environ['bot_token']
|
||||||
|
|
||||||
firebaseConfig = {
|
MONGODB_DATABASE=os.environ['MONGODB_DATABASE']
|
||||||
"apiKey": os.environ['firebase_apiKey'],
|
MONGODB_USERNAME=os.environ['MONGODB_USERNAME']
|
||||||
"authDomain": os.environ['firebase_authDomain'],
|
MONGODB_PASSWORD=os.environ['MONGODB_PASSWORD']
|
||||||
"databaseURL": os.environ['firebase_databaseURL'],
|
MONGODB_HOSTNAME=os.environ['MONGODB_HOSTNAME']
|
||||||
"projectId": os.environ['firebase_projectId'],
|
MONGODB_PORT=os.environ['MONGODB_PORT']
|
||||||
"storageBucket": os.environ['firebase_storageBucket'],
|
|
||||||
}
|
|
||||||
|
6
src/config/handlers.py
Normal file → Executable file
@ -1,6 +1,6 @@
|
|||||||
from ..handlers.inHandler import InHandler
|
from handlers.inHandler import InHandler
|
||||||
from ..handlers.outHandler import OutHandler
|
from handlers.outHandler import OutHandler
|
||||||
from ..handlers.mentionHandler import MentionHandler
|
from handlers.mentionHandler import MentionHandler
|
||||||
|
|
||||||
handlers = [
|
handlers = [
|
||||||
InHandler(),
|
InHandler(),
|
||||||
|
30
src/database/databaseClient.py
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
class DatabaseClient():
|
||||||
|
mongoClient: MongoClient
|
||||||
|
database: Database
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
uri = "mongodb://%s:%s@%s:%s/%s?authSource=admin" % (
|
||||||
|
MONGODB_USERNAME, quote_plus(MONGODB_PASSWORD),
|
||||||
|
MONGODB_HOSTNAME, MONGODB_PORT, MONGODB_DATABASE
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mongoClient = MongoClient(uri)
|
||||||
|
self.database = self.mongoClient[MONGODB_DATABASE]
|
||||||
|
|
||||||
|
def insert(self, collection: str, data: dict) -> None:
|
||||||
|
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:
|
||||||
|
return self.database.get_collection(collection).find_one(query)
|
||||||
|
|
||||||
|
def remove(self, collection: str, data: dict) -> None:
|
||||||
|
self.database.get_collection(collection).remove(data)
|
28
src/entities/chat.py
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
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'])
|
34
src/entities/chatPerson.py
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
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,34 +0,0 @@
|
|||||||
from typing import Iterable
|
|
||||||
from .user import User
|
|
||||||
|
|
||||||
class Group():
|
|
||||||
__id: int
|
|
||||||
__users: Iterable[User] = []
|
|
||||||
|
|
||||||
def __init__(self, id: int) -> None:
|
|
||||||
self.__id = id
|
|
||||||
|
|
||||||
def getId(self) -> int:
|
|
||||||
return self.__id
|
|
||||||
|
|
||||||
def setUsers(self, users: Iterable[User]) -> None:
|
|
||||||
self.__users = users
|
|
||||||
|
|
||||||
def addUser(self, user: User) -> None:
|
|
||||||
self.__users.append(user)
|
|
||||||
|
|
||||||
def removeUser(self, user: User) -> None:
|
|
||||||
for index, groupUser in enumerate(self.__users):
|
|
||||||
if groupUser.getId() == user.getId():
|
|
||||||
del self.__users[index]
|
|
||||||
|
|
||||||
def getUsers(self) -> Iterable[User]:
|
|
||||||
return self.__users
|
|
||||||
|
|
||||||
def hasUser(self, user: User) -> bool:
|
|
||||||
userIds = [int(groupUser.getId()) for groupUser in self.getUsers()]
|
|
||||||
|
|
||||||
if user.getId() in userIds:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
44
src/entities/person.py
Executable file
@ -0,0 +1,44 @@
|
|||||||
|
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'])
|
@ -1,19 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
class User():
|
|
||||||
__id: int
|
|
||||||
__username: Optional[str]
|
|
||||||
__groupId: int
|
|
||||||
|
|
||||||
def __init__(self, id: int, username: Optional[str]) -> None:
|
|
||||||
self.__id = id
|
|
||||||
self.__username = username
|
|
||||||
|
|
||||||
def getId(self) -> int:
|
|
||||||
return self.__id
|
|
||||||
|
|
||||||
def getUsername(self) -> Optional[str]:
|
|
||||||
return self.__username
|
|
||||||
|
|
||||||
def getGroupId(self) -> int:
|
|
||||||
return self.__groupId
|
|
@ -1,34 +0,0 @@
|
|||||||
import pyrebase
|
|
||||||
from pyrebase.pyrebase import Database as FirebaseDB
|
|
||||||
from .config.credentials import firebaseConfig
|
|
||||||
|
|
||||||
|
|
||||||
class FirebaseProxy():
|
|
||||||
db: FirebaseDB
|
|
||||||
|
|
||||||
# Group specific values
|
|
||||||
group_index: str = 'groups'
|
|
||||||
|
|
||||||
# User specific values
|
|
||||||
id_index: str = 'id'
|
|
||||||
name_index: str = 'name'
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
firebase = pyrebase.pyrebase.initialize_app(firebaseConfig)
|
|
||||||
self.db = firebase.database()
|
|
||||||
|
|
||||||
def getChilds(self, *childs: str) -> FirebaseDB:
|
|
||||||
current = self.db
|
|
||||||
|
|
||||||
for child_index in childs:
|
|
||||||
current = current.child(child_index)
|
|
||||||
|
|
||||||
return current
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getGroupPath(groupId: int) -> str:
|
|
||||||
return f'{FirebaseProxy.group_index}/{groupId}'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getUserPath(userId: int, groupId: int) -> str:
|
|
||||||
return f'{groupId}_{userId}'
|
|
3
src/handlers/handlerInterface.py
Normal file → Executable file
@ -16,3 +16,6 @@ class HandlerInterface:
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def getCommandName(self) -> str: raise Exception('getCommandName method is not implemented')
|
def getCommandName(self) -> str: raise Exception('getCommandName method is not implemented')
|
||||||
|
|
||||||
|
def reply(self, update: Update, message: str) -> None:
|
||||||
|
update.effective_message.reply_markdown_v2(text=message)
|
||||||
|
30
src/handlers/inHandler.py
Normal file → Executable file
@ -1,12 +1,11 @@
|
|||||||
from ..config.contents import opted_in_successfully, opted_in_failed
|
from config.contents import opted_in_successfully, opted_in_failed
|
||||||
from ..entities.user import User
|
from repositories.relationRepository import RelationRepository
|
||||||
from ..repositories.groupRepository import GroupRepository
|
from database.databaseClient import DatabaseClient
|
||||||
from .handlerInterface import HandlerInterface
|
from handlers.handlerInterface import HandlerInterface
|
||||||
from telegram.ext.callbackcontext import CallbackContext
|
from telegram.ext.callbackcontext import CallbackContext
|
||||||
from telegram.ext.commandhandler import CommandHandler
|
from telegram.ext.commandhandler import CommandHandler
|
||||||
from telegram.update import Update
|
from telegram.update import Update
|
||||||
|
|
||||||
|
|
||||||
class InHandler(HandlerInterface):
|
class InHandler(HandlerInterface):
|
||||||
botHandler: CommandHandler
|
botHandler: CommandHandler
|
||||||
commandName: str = 'in'
|
commandName: str = 'in'
|
||||||
@ -18,18 +17,19 @@ class InHandler(HandlerInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, update: Update, context: CallbackContext) -> None:
|
def handle(self, update: Update, context: CallbackContext) -> None:
|
||||||
groupRepository = GroupRepository()
|
personId = update.effective_user.id
|
||||||
group = groupRepository.get(update.effective_chat.id)
|
chatId = update.effective_chat.id
|
||||||
user = User(update.effective_user.id, update.effective_user.username)
|
username = update.effective_user.username
|
||||||
|
|
||||||
if group.hasUser(user):
|
relationRepository = RelationRepository()
|
||||||
update.message.reply_markdown_v2(text=opted_in_failed)
|
relation = relationRepository.get(chatId, personId)
|
||||||
|
|
||||||
|
if relation:
|
||||||
|
self.reply(update, opted_in_failed)
|
||||||
return
|
return
|
||||||
|
|
||||||
group.addUser(user)
|
relationRepository.save(chatId, personId, username)
|
||||||
groupRepository.save(group)
|
self.reply(update, opted_in_successfully)
|
||||||
|
|
||||||
update.message.reply_markdown_v2(text=opted_in_successfully)
|
|
||||||
|
|
||||||
def getBotHandler(self) -> CommandHandler:
|
def getBotHandler(self) -> CommandHandler:
|
||||||
return self.botHandler
|
return self.botHandler
|
||||||
|
33
src/handlers/mentionHandler.py
Normal file → Executable file
@ -1,9 +1,9 @@
|
|||||||
from ..config.contents import mention_failed
|
from typing import Iterable
|
||||||
from ..entities.group import Group
|
from config.contents import mention_failed
|
||||||
from ..entities.user import User
|
from entities.person import Person
|
||||||
from ..firebaseProxy import FirebaseProxy
|
from handlers.handlerInterface import HandlerInterface
|
||||||
from ..repositories.groupRepository import GroupRepository
|
from repositories.relationRepository import RelationRepository
|
||||||
from .handlerInterface import HandlerInterface
|
from repositories.personRepository import PersonRepository
|
||||||
from telegram.ext.callbackcontext import CallbackContext
|
from telegram.ext.callbackcontext import CallbackContext
|
||||||
from telegram.ext.commandhandler import CommandHandler
|
from telegram.ext.commandhandler import CommandHandler
|
||||||
from telegram.update import Update
|
from telegram.update import Update
|
||||||
@ -20,11 +20,15 @@ class MentionHandler(HandlerInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, update: Update, context: CallbackContext) -> None:
|
def handle(self, update: Update, context: CallbackContext) -> None:
|
||||||
groupId = update.effective_chat.id
|
relationRepository = RelationRepository()
|
||||||
groupRepository = GroupRepository()
|
persons = relationRepository.getPersonsForChat(update.effective_chat.id)
|
||||||
mentionMessage = self.buildMentionMessage(groupRepository.get(id=groupId))
|
|
||||||
|
if not persons:
|
||||||
|
self.reply(update, mention_failed)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.reply(update, self.buildMentionMessage(persons))
|
||||||
|
|
||||||
update.message.reply_markdown_v2(text=mentionMessage)
|
|
||||||
|
|
||||||
def getBotHandler(self) -> CommandHandler:
|
def getBotHandler(self) -> CommandHandler:
|
||||||
return self.botHandler
|
return self.botHandler
|
||||||
@ -32,11 +36,10 @@ class MentionHandler(HandlerInterface):
|
|||||||
def getCommandName(self) -> str:
|
def getCommandName(self) -> str:
|
||||||
return self.commandName
|
return self.commandName
|
||||||
|
|
||||||
def buildMentionMessage(self, group: Group) -> str:
|
def buildMentionMessage(self, persons: Iterable[Person]) -> str:
|
||||||
result = ''
|
result = ''
|
||||||
|
|
||||||
for user in group.getUsers():
|
for person in persons:
|
||||||
username = user.getUsername() or user.getId()
|
result += f'*[{person.getUsername()}](tg://user?id={person.getId()})* '
|
||||||
result += f'*[{username}](tg://user?id={user.getId()})* '
|
|
||||||
|
|
||||||
return result or mention_failed
|
return result
|
||||||
|
28
src/handlers/outHandler.py
Normal file → Executable file
@ -1,11 +1,11 @@
|
|||||||
from ..config.contents import opted_off_successfully, opted_off_failed
|
from config.contents import opted_off_successfully, opted_off_failed
|
||||||
from ..entities.user import User
|
from handlers.handlerInterface import HandlerInterface
|
||||||
from ..repositories.groupRepository import GroupRepository
|
|
||||||
from .handlerInterface import HandlerInterface
|
|
||||||
from telegram.ext.callbackcontext import CallbackContext
|
from telegram.ext.callbackcontext import CallbackContext
|
||||||
from telegram.ext.commandhandler import CommandHandler
|
from telegram.ext.commandhandler import CommandHandler
|
||||||
from telegram.update import Update
|
from telegram.update import Update
|
||||||
|
|
||||||
|
from repositories.relationRepository import RelationRepository
|
||||||
|
|
||||||
|
|
||||||
class OutHandler(HandlerInterface):
|
class OutHandler(HandlerInterface):
|
||||||
botHandler: CommandHandler
|
botHandler: CommandHandler
|
||||||
@ -18,18 +18,18 @@ class OutHandler(HandlerInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def handle(self, update: Update, context: CallbackContext) -> None:
|
def handle(self, update: Update, context: CallbackContext) -> None:
|
||||||
groupRepository = GroupRepository()
|
personId = update.effective_user.id
|
||||||
group = groupRepository.get(update.effective_chat.id)
|
chatId = update.effective_chat.id
|
||||||
user = User(update.effective_user.id, update.effective_user.username)
|
|
||||||
|
|
||||||
if group.hasUser(user):
|
relationRepository = RelationRepository()
|
||||||
group.removeUser(user)
|
relation = relationRepository.get(chatId, personId)
|
||||||
groupRepository.save(group)
|
|
||||||
|
if not relation:
|
||||||
update.message.reply_markdown_v2(text=opted_off_successfully)
|
self.reply(update, opted_off_failed)
|
||||||
return
|
return
|
||||||
|
|
||||||
update.message.reply_markdown_v2(text=opted_off_failed)
|
relationRepository.remove(relation)
|
||||||
|
self.reply(update, opted_off_successfully)
|
||||||
|
|
||||||
def getBotHandler(self) -> CommandHandler:
|
def getBotHandler(self) -> CommandHandler:
|
||||||
return self.botHandler
|
return self.botHandler
|
||||||
|
18
src/repositories/chatRepository.py
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
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,48 +0,0 @@
|
|||||||
from ..entities.group import Group
|
|
||||||
from ..entities.user import User
|
|
||||||
from ..firebaseProxy import FirebaseProxy
|
|
||||||
|
|
||||||
|
|
||||||
class GroupRepository():
|
|
||||||
firebase: FirebaseProxy
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.firebase = FirebaseProxy()
|
|
||||||
|
|
||||||
def get(self, id: int) -> Group:
|
|
||||||
group = Group(id)
|
|
||||||
fbData = self.firebase.getChilds(FirebaseProxy.group_index, id).get()
|
|
||||||
users = []
|
|
||||||
|
|
||||||
for userData in fbData.each() or []:
|
|
||||||
userData = userData.val()
|
|
||||||
users.append(
|
|
||||||
User(
|
|
||||||
userData.get(FirebaseProxy.id_index),
|
|
||||||
userData.get(FirebaseProxy.name_index)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
group.setUsers(users)
|
|
||||||
|
|
||||||
return group
|
|
||||||
|
|
||||||
def save(self, group: Group) -> None:
|
|
||||||
users = {}
|
|
||||||
|
|
||||||
if not group.getUsers():
|
|
||||||
self.remove(group)
|
|
||||||
|
|
||||||
for user in group.getUsers():
|
|
||||||
users[user.getId()] = {
|
|
||||||
FirebaseProxy.id_index: user.getId(),
|
|
||||||
FirebaseProxy.name_index: user.getUsername()
|
|
||||||
}
|
|
||||||
|
|
||||||
self.firebase.getChilds(
|
|
||||||
FirebaseProxy.group_index,
|
|
||||||
group.getId()
|
|
||||||
).update(users)
|
|
||||||
|
|
||||||
def remove(self, group: Group) -> None:
|
|
||||||
self.firebase.getChilds(FirebaseProxy.group_index, group.getId()).remove()
|
|
27
src/repositories/personRepository.py
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
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())
|
56
src/repositories/relationRepository.py
Executable file
@ -0,0 +1,56 @@
|
|||||||
|
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())
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
from ..firebaseProxy import FirebaseProxy
|
|
||||||
|
|
||||||
|
|
||||||
class UserRepository():
|
|
||||||
firebaseProxy: FirebaseProxy
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.firebaseProxy = FirebaseProxy()
|
|
||||||
|
|
||||||
# TODO : this repository needs to handle user save/deletion/update
|
|
||||||
# right now, all of those above is handled by GroupRepository
|
|
||||||
|
|
3
requirements.txt → src/requirements.txt
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
python-dotenv==0.19.0
|
python-dotenv==0.19.0
|
||||||
python-telegram-bot==13.7
|
python-telegram-bot==13.7
|
||||||
Pyrebase==3.0.27
|
pymongo==3.12.0
|
||||||
|
names==0.3.0
|