django-tbot-messages 0.2.2
Module for editable messages in your telegram bot
License: MIT

# django-tbot-messages

------------

#### Структура модуля
 - [Команда сreate_messages](#create_messages)
 - [utils.py](#utilspy)
 - [messages.py](#messagespy)
 - [views.py](#viewspy)

[Быстрый старт с модулем](#quickstart)  
[Локализация сообщений](#использование-локализации)  
[Система диалогов](#диалоги)

------------------------------------------------------------

### Описание  
Модуль служит для удобной интеграции функции отключаемых сообщений с возможностью переводов.  
Также в модуле предусмотрена простая машина состояний и система диалогов.

------------

### create_messages

Команда `manage.py create_messages` добавляет в базу данных сообщения описанные 
в переменной `BOT_MESSAGES` в файле `settings.py` проекта.

> Формат переменной BOT_MESSAGES:
> ```python
> BOT_MESSAGES = {
>   'ChooseLanguage': {
>        'text': 'Выберите язык',
>        'buttons': [
>            {'reply': True, 'row': 0, 'text': 'Русский'},
>            {'reply': True, 'row': 0, 'text': 'Українська'}
>        ]
>    },
>    'Promo': {
>        'text': 'Вам доступен бесплатный тестовый 2ух недельный период на максимальных возможностях!\n',
>        'text_ua': 'Промо українською',
>    },
>    'Menu': {
>        'text': 'Вам доступно главное меню.\n\n'
>                'В разделе "📝 Создать подписку" вы сможете создать запрос для поиска объявлений по заданным вами параметрам\n'
>                'В разделе "🗂 Мои подписки" вы сможете просмотреть/удалить созданные ранее вами запросы поиска авто.\n'
>                'В разделе "👨‍🔧 Техподдержка" вы сможете отправить сообщение нашему Администратору и получить ответ или консультацию\n'
>                'С помощью функции "🙅 Не беспокоить" вы сможете приостановить поиск объявлений и позднее его "🙋‍♂Восстановить".\n'
>                'В разделе "👤 Мой профиль" вы сможете просмотреть статус вашего текущего тарифа и продлить тариф.\n',
>        'text_ua': 'Меню українською',
>        'buttons': [
>            {'reply': True, 'row': 0, 'text': '🗂 Мои подписки', 'text_ua': '🗂 Мої підписки'},
>            {'reply': True, 'row': 0, 'text': '📝 Создать подписку', 'text_ua': '📝 Створити підписку'},
>            {'reply': True, 'row': 1, 'text': '👨‍🔧 Техподдержка', 'text_ua': '👨‍🔧 Техпідтримка'},
>            {'reply': True, 'row': 1, 'text': '🙅 Не беспокоить', 'text_ua': '🙅 Не турбувати'},
>            {'reply': True, 'row': 2, 'text': '👤 Мой профиль и тарифы', 'text_ua': '👤 Мій профіль та тарифи'},
>        ]
>    }
> }
>```

Конечно, помещать весь словарь в settings.py вас никто не просит...  
Всегда можно импортировать его из другого файла дабы не засорять настройки:

> settings.py
> ```python 
>  ...
>  from tbot.translations import messages
>  BOT_MESSAGES = messages
>  ...
> ```

Словарь `BOT_MESSAGES` состоит из пар имени сообщения в БД и его параметров.  
Среди параметров указываются ключи `text_{locale}` или просто `text` если мы хотим 
использовать это значение по умолчанию.

Также, среди параметров сообщения есть ключ `buttons`, значением которого является массив кнопок. `buttons` можно не указывать если сообщение не содержит кнопок. 
Каждая кнопка это словарь с параметрами:

> ```
>   text         : str   текст кнопки используемый по умолчанию
>   text_{locale}: str   текст кнопки используемый если в user_data есть ключ locale (не обязательно)
>   callback_data: str   callback_data кнопки
>   reply        : bool  при значении True кнопка становится ReplyButton (если параметр не указан или равен False - по умолчанию кнопка InlineKeyboardButton и требует параметр callback_data)
>   inline_mode  : bool  при значении True кнопка обретает параметр switch_inline_query_chat и становится "точкой входа" в инлайн режим бота
>   row          : int   номер ряда (начиная с нулевого), в котором будет отображена кнопка
>   * url        : str   пока что такого параметра нет, но всему своё время!
> ```

После того как вы создали словарь `BOT_MESSAGES`, запускайте команду `manage.py create_messages`,
если вы добавляли локализацию текста (ключи `text_{locale}`), вы увидите перечень добавленных 
локализаций и вопрос:

`ARE YOU SURE YOU WANT TO ADD FOUND LOCALES? (Y/n)::`

После нажатия Enter команда сгенерирует новые модели `BotMessage` и `Button` которые будут содержать 
поля с локализованным текстом. 

### Дисклеймер
Крайне рекомендую (на момент версии 0.2) выполнять команду `manage.py makemigrations <app>` с указанием параметра `app`!  
Это избавит вас от случайного удаления модели `BotMessage` и перезаписи этой самой модели сгенерированной моделью с локализованным текстом.


### utils.py

В этом файле вы найдете разные фильтры и функции облегчающие разработку

### messages.py

В `messages.py` присутствуют классы `BaseMessages` и `BaseDialogs`, работа с ними показывается [тут](#quickstart) и [тут](#диалоги) соответственно. 

## Quickstart

Прежде всего, установите данный модуль
```
pip install django-tbot-messages
```
Если модули [tbot-base](https://pypi.org/project/django-tbot-base/) и [bot-storage](https://pypi.org/project/bot-storage/) не были установлены в вашем проекте, 
не проблема - они указаны в зависимостях к данному модулю.


Далее, будет описан пример создания проекта с использованием `tbot_messages`

Инициализируйте приложение `tbot` с помощью `manage.py startapp tbot`  
Добавьте их в `INSTALLED_APPS` в `settings.py` проекта  

```python
INSTALLED_APPS = [
    ...
    'tbot',
    'tbot_messages',
]
```  

Не забудьте добавить BOT_STORAGE в `settings.py`, указав обьект хранилища

```python 
storage.py

from os import getenv
from bot_storage.storage import RedisStorage

storage = RedisStorage(
    host=getenv('REDIS_HOST', 'localhost'),
    username=getenv('REDIS_USER', None),
    password=getenv('REDIS_PASSWORD', None)
)
```

```python
settings.py

from .storage import storage
BOT_STORAGE = storage
```

Также, создадим сообщения нашего бота в файле `translations.py`

```python
messages = {
    'SupportText': {
        'text_ru': 'Отправьте текст вашего сообщения. 🙂',
        'text_ua': 'Надішліть ваш запит',
        'buttons': [
            {'reply': True, 'row': 0, 'text_ru': 'Главное меню', 'text_ua': 'Головне меню'},
        ]
    },
    'SupportSent': {
        'text_ru': 'Я отправил ваш вопрос Администратору!🙂 Он ответит вам в ближайшее время!',
        'text_ua': 'Я надіслав ваше питання адміну. Очікуйте',
    },
    'SupportAnswer': {
        'text_ru': 'Ответ от Администратора:\n'
                ' - <code>{text}</code>',
        'text_ua': 'Відповідь адміна: <code>{text}</code>',
    }
}
```

Укажите значение `BOT_MESSAGES` в `settings.py`

```python
 # tbot_base configuration
 BOT_HANDLERS = [
    'tbot.dispatcher',
    'tbot.handlers',
 ]

 from tbot.storage import storage
 from tbot.translations import messages

 # instance of storage (such as redis, db or another)
 BOT_STORAGE = storage
 BOT_MESSAGES = messages
```

> Настройка `BOT_HANDLERS` используется при запуске проекта, она служит для 
> последовательной загрузки всех модулей с хендлерами вашего бота. Данная настройка подразумевается
> при использовании базового модуля [tbot_base](https://pypi.org/project/django-tbot-base/)

Окей, теперь можем выполнять миграции
```
manage.py makemigrations && manage.py migrate
```

Выполняем команду `manage.py create_messages` и подтверждаем генерацию моделей с локализациями нажатием клавиши Enter (всегда можно отказаться написав `n`)  

Бинго! Сообщения и кнопки создались, теперь переходим к классу `BaseMessages`  
Создадим файл `messages.py` и в нем опишем класс `Messages` унаследованный от `BaseMessages`

```python
from tbot_messages.messages import BaseMessages

class Messages(BaseMessages):
    SupportText = 'SupportText'
    SupportSent = 'SupportSent'
    SupportAnswer = 'SupportAnswer'
```

Мы должны присвоить обьектам сообщений их названия в БД (однажды я автоматизирую этот процесс, но пока наслаждайтесь)

А теперь можем смело использовать обьект сообщения и отправлять его, если оно активно

`dispatcher.py`
```python
from telebot import types
from tbot.messages import Messages
from tbot_base.bot import tbot

@tbot.message_handler(commands=['start'])
def support_text(message: types.Message):
    user_id = message.from_user.id
    Messages.SupportText.send_if_active(user_id)
```

### Использование локализации
 
Язык который будет использован при отправке сообщения во время
вызова метода `send_if_active()` зависит от значения `locale` в user_data.  

Чтобы предложить пользователю выбрать язык, сделаем простой хендлер нажатия
реплай кнопок `Русский` и `Українська`

dispatcher.py
```python
from telebot import types
from tbot_base.bot import tbot
from .storage import storage

@tbot.message_handler(func=lambda message: message.text in ('Русский', 'Українська'))
def language_handler(message: types.Message):
    user_id = message.from_user.id
    if message.text == 'Русский':
        locale = None
    elif message.text == 'Українська':
        locale = 'ua'
    storage.update_user_data(user_id, 'locale', locale)
```

В данном случае, мы используем локализацию по умолчанию если был выбран русский язык.  
Т.е поле `text` будет использовано при `locale = None`, но как только `locale`
приобретает какое либо значение, текст сообщений берётся из `text_{locale}`


### Диалоги

Диалоги идеально подходят создания форм.  
Например, сделаем простую регистрацию используя класс `Dialogs` и `Messages` 

`messages.py`
```python
from tbot_messages.messages import BaseMessages, BaseDialogs

class Messages(BaseMessages):
    AskName = 'AskName'
    AskPhone = 'AskPhone'
    AskEmail = 'AskEmail'

class Dialogs(BaseDialogs):
    REG = (Messages.AskName, Messages.AskPhone, Messages.AskEmail)
```

И теперь напишем логику регистрации

`dispatcher.py`
```python
from telebot import types
from tbot_base.bot import tbot
from .storage import storage as st
from .messages import Dialogs
from .models import User

@tbot.message_handler(commands=['start'])
def start_handler(message: types.Message):
    user_id = message.from_user.id
    user = User.object.filter(id=user_id).first()
    if not user:
        # флаг start в данном случае обязателен
        Dialogs.REG.next_message(user_id, start=True)
    else:
        ...

# state пользователя генерируется автоматически
@tbot.message_handler(func=lambda msg: st.get_user_state(msg.from_user.id) == 'REG:AskName')
def ask_name_handler(message: types.Message):
    name = message.text
    user_id = message.from_user.id
    
    # сохраняем значение в хранилище
    st.update_user_data(user_id, 'name', name)
    
    Dialogs.REG.next_message(user_id)
```

При использовании `.next_message()` перезаписывается `state` пользователя
на `dialog:message`. В данном примере диалог называется `REG`, и при вызове 
`.next_message(start=True)` отправленным сообщением будет первое в кортеже - `AskName`.

Вызвав метод `.next_message()` во второй раз, бот отправит пользователю сообщение 
`AskPhone` и установит его состояние на `REG:AskPhone`