Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added bot commands regarding team info #3

Merged
merged 16 commits into from
Oct 23, 2022
2 changes: 2 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ jobs:
runs-on: ubuntu-20.04
env:
DB_URL: postgresql://user:[email protected]:5432/db
BOT_TOKEN: BOT_TOKEN
BOT_NAME: BOT_NAME

services:

Expand Down
325 changes: 221 additions & 104 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ orjson = "^3.5.3"
alembic = "^1.6.5"
aiohttp = "^3.7.4"
asyncpg = "^0.23.0"
aiogram = "^2.22.2"
uvloop = "^0.17.0"

[tool.poetry.dev-dependencies]
black = "22.3.0"
Expand Down
28 changes: 28 additions & 0 deletions requestor/bot/bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import asyncio

from asyncpg import create_pool

from requestor.db import DBService
from requestor.settings import get_config

from .create_bot import dp
from .handlers import register_handlers


async def main():
config = get_config()
db_config = config.db_config.dict()
pool_config = db_config.pop("db_pool_config")
pool_config["dsn"] = pool_config.pop("db_url")
pool = create_pool(**pool_config)
db_service = DBService(pool=pool)
feldlime marked this conversation as resolved.
Show resolved Hide resolved
try:
register_handlers(dp, db_service, config)
await db_service.setup()
feldlime marked this conversation as resolved.
Show resolved Hide resolved
await dp.start_polling()
finally:
await db_service.cleanup()


if __name__ == "__main__":
asyncio.run(main()) # type: ignore
10 changes: 10 additions & 0 deletions requestor/bot/create_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from aiogram import Bot
from aiogram.dispatcher import Dispatcher

from requestor.settings import get_config

config = get_config()

bot = Bot(token=config.telegram_config.bot_token)

dp = Dispatcher(bot)
164 changes: 164 additions & 0 deletions requestor/bot/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import traceback
import typing as tp
from functools import partial

from aiogram import Dispatcher, types

from requestor.db import DBService, DuplicatedTeamError, TeamNotFoundError
from requestor.log import app_logger
from requestor.models import TeamInfo
from requestor.settings import ServiceConfig


def parse_msg_with_team_info(message: types.Message) -> tp.Optional[TeamInfo]:
command = message.text.split(maxsplit=3)
if len(command) == 4:
_, title, api_base_url, api_key = command
elif len(command) == 3:
_, title, api_base_url = command
api_key = None

try:
return TeamInfo(
title=title, chat_id=message.chat.id, api_base_url=api_base_url, api_key=api_key
)
except NameError:
return None


async def handle(handler, db_service: DBService, message: types.Message) -> None:
try:
await handler(message, db_service)
except Exception:
app_logger.error(traceback.format_exc())
raise


async def start_h(message: types.Message, db_service: DBService) -> None:
reply = (
"Привет! Я бот, который будет проверять сервисы "
"в рамках курса по рекомендательным системам. "
"Наберите /help для вывода списка доступных команд."
)
await message.reply(reply)


async def help_h(event: types.Message, db_service: DBService) -> None:
reply = (
"Список доступных команд:\n"
"/register_team team_name api_host api_key (опционально) - для регистрации команд\n"
feldlime marked this conversation as resolved.
Show resolved Hide resolved
"/update_team team_name api_host api_key (опционально) - для обновления данных команды\n"
"/show_current_team - для вывода данных по зарегистрированной команде\n"
)
await event.reply(reply)


async def register_team_h(message: types.Message, db_service: DBService) -> None:
team_info = parse_msg_with_team_info(message)

if team_info is None:
await message.reply(
"Пожалуйста, введите данные в корректном формате. "
"/register_team team_name api_host api_key (опционально)"
)
return

try:
await db_service.add_team(team_info)
await message.reply(f"Команда `{team_info.title}` успешно зарегистрирована!")
feldlime marked this conversation as resolved.
Show resolved Hide resolved
# TODO: somehow deduplicate code? wrapper?
except DuplicatedTeamError as e:
if e.column == "chat_id":
await message.reply(
"Вы уже регистрировали команду. Если необходимо обновить что-то, "
"пожалуйста, воспользуйтесь командой /update_team."
)
elif e.column == "title":
await message.reply(
f"Команда с именем `{team_info.title}` уже существует. "
"Пожалуйста, выберите другое имя команды."
)
elif e.column == "api_base_url":
await message.reply(
f"Хост: `{team_info.api_base_url}` уже кем-то используется. "
"Пожалуйста, выберите другой хост."
)
else:
await message.reply(e)


async def update_team_h(message: types.Message, db_service: DBService) -> None:
updated_team_info = parse_msg_with_team_info(message)
if updated_team_info is None:
await message.reply(
"Пожалуйста, введите данные в корректном формате. "
"/update_team team_name api_host api_key (опционально)"
)
return

current_team_info = await db_service.get_team_by_chat(message.chat.id)

try:
await db_service.update_team(current_team_info.team_id, updated_team_info)
await message.reply(
feldlime marked this conversation as resolved.
Show resolved Hide resolved
"Данные по вашей команде были обновлены. Воспользуйтесь командой /show_current_team"
)
except TeamNotFoundError:
await message.reply(
"Команда от вашега чата не найдена. Скорее всего, что вы еще не регистрировались."
feldlime marked this conversation as resolved.
Show resolved Hide resolved
)
# TODO: somehow deduplicate code? wrapper?
except DuplicatedTeamError as e:
if e.column == "chat_id":
await message.reply(
"Вы уже регистрировали команду. Если необходимо обновить что-то, "
feldlime marked this conversation as resolved.
Show resolved Hide resolved
"пожалуйста, воспользуйтесь командой /update_team."
)
elif e.column == "title":
await message.reply(
f"Команда с именем `{updated_team_info.title}` уже существует. "
"Пожалуйста, выберите другое имя команды."
)
elif e.column == "api_base_url":
await message.reply(
f"Хост: `{updated_team_info.api_base_url}` уже кем-то используется. "
"Пожалуйста, выберите другой хост."
)
else:
await message.reply(e)


async def show_current_team_h(message: types.Message, db_service: DBService) -> None:
try:
team_info = await db_service.get_team_by_chat(message.chat.id)
await message.reply(
f"Команда: {team_info.title}\n"
f"Хост: {team_info.api_base_url}\n"
f"API Токен: {team_info.api_key if team_info.api_key is not None else 'Отсутствует'}\n"
)
except TeamNotFoundError:
await message.reply(
"Команда от вашега чата не найдена. Скорее всего, что вы еще не регистрировались."
feldlime marked this conversation as resolved.
Show resolved Hide resolved
)


async def other_messages_h(message: types.Message, db_service: DBService) -> None:
await message.reply("Я не поддерживаю Inline команды. Пожалуйста, воспользуйтесь /help.")
feldlime marked this conversation as resolved.
Show resolved Hide resolved


def register_handlers(dp: Dispatcher, db_service: DBService, config: ServiceConfig) -> None:
bot_name = config.telegram_config.bot_name
command_handlers_mapping = {
"start": start_h,
"help": help_h,
"register_team": register_team_h,
"update_team": update_team_h,
"show_current_team": show_current_team_h,
}

for command, handler in command_handlers_mapping.items():
dp.register_message_handler(partial(handle, handler, db_service), commands=[command])

dp.register_message_handler(
partial(handle, other_messages_h, db_service), regexp=rf"@{bot_name}"
)
2 changes: 2 additions & 0 deletions requestor/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class TeamInfo(BaseModel):
title: str
chat_id: int
api_base_url: str
# TODO: move from python3.10 to 3.8 with poetry to fix issues with pylint
# https://github.com/PyCQA/pylint/issues/1498#issuecomment-717978456
api_key: tp.Optional[str]


Expand Down
7 changes: 7 additions & 0 deletions requestor/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,20 @@ class DBConfig(Config):
db_pool_config: DBPoolConfig


class TelegramConfig(Config):
bot_token: str
bot_name: str


class ServiceConfig(Config):
log_config: LogConfig
db_config: DBConfig
telegram_config: TelegramConfig


def get_config() -> ServiceConfig:
return ServiceConfig(
log_config=LogConfig(),
db_config=DBConfig(db_pool_config=DBPoolConfig()),
telegram_config=TelegramConfig(),
)