В этой статье мы рассмотрим важность подключения базовой аналитики к боту и какие преимущества это может принести.

Введение

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

Здесь на помощь приходит подключение аналитики к боту. Интеграция аналитических инструментов и методов позволяет собирать, анализировать и интерпретировать данные о взаимодействии пользователей с ботом. Это открывает перед нами возможность получить ценные инсайты, которые помогут оптимизировать работу бота, улучшить пользовательский опыт и принимать обоснованные решения на основе данных.

Подключение аналитики к боту является неотъемлемой частью развития и совершенствования чат-ботов в нашей динамичной цифровой эпохе. Оно позволяет превратить бота из простого инструмента коммуникации в мощный инструмент анализа данных, который поможет вашему бизнесу сформировать эффективную стратегию и достичь поставленных целей. Давайте рассмотрим все преимущества и возможности, которые открывает перед нами подключение аналитики к боту.

Какие данные мы сегодня получим

В данном примере, мы научимся:

  • видеть количество «живых» и «мёртвых» пользователей;

  • видеть количество сообщений, отправленных через бота;

  • видеть сами сообщения;

  • строить графики.

Инструменты

  • Python 3 (aiogram — фреймворк для создания ботов);

  • MongoDB — база данных, которая будет служить источником для построения дашборда;

  • Telegram — лучший мессенджер для работы с ботами;

  • Redash — self-hosted сервис для построения дашбордов.

Для начала напишем бота

В качестве примера, напишем простейшего бота на Python 3, который позволяет обмениваться анонимными сообщениями.

Принцип работы

Пользователю предоставляется персональная ссылка на бота, которая содержит идентификатор пользователя, которому необходимо отправить сообщение. Он может разместить её в своих соцсетях. Тот, кто хочет отправить пользователю анонимное сообщение, должен перейти по этой ссылке. После этого, бот получит команду /start с полезной нагрузкой в виде идентификатора пользователя в Телеграм.

Создаём бота в Telegram

Идём к @BotFather и создаём бота:

Подключаем либы

Подключаем библиотеку для создания телеграм ботов:

pip3 install aiogram

А также для работы с MongoDB:

pip3 install pymongo

Пишем код

Создаём файл main.py и прописываем начало для любого бота:

bot = Bot(token='ЗДЕСЬ ТОКЕН ОТ BotFather')
storage = MemoryStorage() # хранилище, которое позволяет запоминать состояния 
                          # диалогов с пользователями
dp = Dispatcher(bot, storage=storage)

Добавим одно единственное состояние, которое нам понадобится для отправки сообщений:

class Stage(StatesGroup):
    send_message_to = State()

Пропишем обработчик для команды /start:

@dp.message_handler(commands=['start'], state=['*', Stage.send_message_to])
async def start(message: types.Message, state: FSMContext):
    args = message.get_args() # получаем полезную нагрузку из команды /start
    payload = decode_payload(args)
    
    if payload: # сюда заходим, если перешли по чьей-то ссылке
        async with state.proxy() as data:
            data['user-id'] = payload # сохраняем идентификатор реципиента в контексте диалога 

        await Stage.send_message_to.set() # устанавливаем состояние отправки сообщения реципиенту
        
        start_text = 'Привет! Я бот для анонимного общения. Отправьте сообщение тому, кто опубликовал эту ссылку. Он не узнает, от кого пришло сообщение.'
        await message.reply(start_text)
        return

    # если полезной нагрузки нет, значит пользователь просто нажал /start
    link = await get_start_link(message.from_user.id, encode=True) # создаём персональную ссылку
    content = f'Твоя персональная ссылка: {link}'
    await message.reply(content)

Напишем обработчик для отправки сообщений:

@dp.message_handler(state=Stage.send_message_to, content_types=ContentType.ANY)
async def process_message(message: types.Message, state: FSMContext):
    inline_btn = InlineKeyboardButton('Ответить', callback_data=f'answer_{message.from_user.id}')
    inline_kb = InlineKeyboardMarkup().add(inline_btn)
    async with state.proxy() as data:
        if 'answer_user_id' in data:
            user_id = data['answer_user_id'] # если мы отвечаем на сообщение
            await message.answer('Ваш ответ был доставлен.')
            await message.bot.send_message(user_id, 'Вы получили ответ.')
        else:
            user_id = data['user-id'] # если мы отправляем новое сообщение
            await message.answer('Ваше сообщение было отпралено.')
            await message.bot.send_message(user_id, 'Вы получили новое анонимное сообщение.')
        if message.video:
            await message.bot.send_video(user_id, message.video.file_id, reply_markup=inline_kb)
            content = message.video.as_json()
        elif message.video_note:
            await message.bot.send_video_note(user_id, message.video_note.file_id, reply_markup=inline_kb)
            content = message.video_note.as_json()
        elif message.voice:
            await message.bot.send_voice(user_id, message.voice.file_id, reply_markup=inline_kb)
            content = message.voice.as_json()
        elif message.photo:
            await message.bot.send_photo(user_id, message.photo[-1].file_id, reply_markup=inline_kb)
            content = message.photo[-1].as_json()
        elif message.audio:
            await message.bot.send_audio(user_id, message.audio.file_id, reply_markup=inline_kb)
            content = message.audio.as_json()
        elif message.sticker:
            await message.bot.send_sticker(user_id, message.sticker.file_id, reply_markup=inline_kb)
            content = message.sticker.file_id
        elif message.document:
            await message.bot.send_document(user_id, message.document.file_id, reply_markup=inline_kb)
            content = message.document.as_json()
        else:
            await message.bot.send_message(user_id, message.text, reply_markup=inline_kb)
            content = message.text
    await state.finish()

В примере выше мы реализовали возможность отправки различных типов сообщений, но можно оставить только текст.

Напишем обработчик нажатий на кнопку «Ответить»:

answer_regexp = re.compile("answer_(.*)")


@dp.callback_query_handler(lambda c: answer_regexp.match(c.data), state=['*', Stage.send_message_to])
async def process_callback_answer(callback_query: types.CallbackQuery, state: FSMContext):
    await bot.answer_callback_query(callback_query.id)
    await bot.send_message(callback_query.from_user.id, 'Введите свой ответ.')
    async with state.proxy() as data:
        data['answer_user_id'] = answer_regexp.match(callback_query.data).group(1)
    await Stage.send_message_to.set()

Остаётся запустить бота:

if __name__ == '__main__':
    executor.start_polling(dp, skip_updates=False)

Работа с базой данных

Создадим файл db.py и разметим методы для работы с БД.

Подключаемся к БД:

client = MongoClient('ТУТ CONNECTION_STRING')
db = client['AskFM']

users = db['users']
messages = db['messages']

Добавление пользователя:

def add_user(user_id, username, datetime):
    user_filter = {
        'user_id': user_id
    }
    user = users.find_one(user_filter)
    if user and user['status'] == 'deleted':
        update = {
            'status': 'active'
        }
        users.update_one(user_filter, {'$set': update})
        return
    elif user:
        return
    data = {
        'user_id': user_id,
        'username': username,
        'datetime': datetime,
        'status': 'active'
    }
    users.insert_one(data)

Удаляем пользователя:

def delete_user(user_id):
    user_filter = {
        'user_id': user_id
    }
    update = {
        'status': 'deleted'
    }
    users.update_one(user_filter, {'$set': update})

Добавляем сообщение:

def add_message(user_id_from, user_id_to, username_from, username_to, content, message_type, datetime):
    message = {
        "user_id_from": user_id_from,
        "user_id_to": user_id_to,
        "username_from": username_from,
        "username_to": username_to,
        "content": content,
        "message_type": message_type,
        "datetime": datetime
    }
    messages.insert_one(message)

Интегрируем работу с БД в бота

import db

Напишем обработчик, который отслеживает активацию и деактивацию бота:

@dp.my_chat_member_handler()
async def add_to_channel(update: ChatMemberUpdated):
    if update.new_chat_member.status == 'member':
        db.add_user(update.from_user.id, update.from_user.username, time.time())
    elif update.new_chat_member.status == 'kicked':
        db.delete_user(update.from_user.id)

Добавим регистрацию пользователя в метод /start:

db.add_user(message.from_user.id, message.from_user.username, time.time())

Итоговый код обработчика:

@dp.message_handler(commands=['start'], state=['*', Stage.send_message_to])
async def start(message: types.Message, state: FSMContext):
    args = message.get_args() # получаем полезную нагрузку из команды /start
    payload = decode_payload(args)
    
    if payload: # сюда заходим, если перешли по чьей-то ссылке
        async with state.proxy() as data:
            data['user-id'] = payload # сохраняем идентификатор реципиента в контексте диалога 

        await Stage.send_message_to.set() # устанавливаем состояние отправки сообщения реципиенту
        
        start_text = 'Привет! Я бот для анонимного общения. Отправьте сообщение тому, кто опубликовал эту ссылку. Он не узнает, от кого пришло сообщение.'
        await message.reply(start_text)

        db.add_user(message.from_user.id, message.from_user.username, time.time()) # сюда добавили
        
        return

    # если полезной нагрузки нет, значит пользователь просто нажал /start
    link = await get_start_link(message.from_user.id, encode=True) # создаём персональную ссылку
    content = f'Твоя персональная ссылка: {link}'
    await message.reply(content)

    db.add_user(message.from_user.id, message.from_user.username, time.time()) # и вот сюда добавили

Зачем добавлять регистрацию пользователя в метод /start , если у нас есть отдельный обработчик, который смотрит активацию и деактивацию бота? Ответ: тот метод отрабатывает не всегда корректно.

Добавим регистрацию сообщений в самый конец обработчика отправки сообщений:

user_to = await bot.get_chat_member(user_id, user_id)
if 'answer_user_id' in data:
    message_type = 'answer'
else:
    message_type = 'question'
db.add_message(message.from_user.id,
               user_id, message.from_user.username,
               user_to.user.username, content,
               message_type, time.time())

На этом с кодом закончили. Готовый код можно посмотреть тут.

Запускаем бота и пробуем его потестить:

python3 main.py

В следующей части мы рассмотрим создание самого дашборда.

P.S.: Моя первая статья на Хабре, буду рад конструктивной критике.

Комментарии (0)