Чат-бот Telegram с помощью python-telegram-bot | Сообщения между клиентами и операторами испорчены

У нас есть 3 оператора, и я хочу, чтобы они были заняты, если они вошли в чат с кем-то. Когда чат с клиентом закончится, оператор будет свободен для других клиентов и сможет общаться. Я случайно удалил часть кода, где оператор после входа в чат становится занятым (is_available=False), а после завершения чата становится доступным (is_available=True).

Стек: Django, python-telegram-bot

models.py:

from django.db import models

class CustomUser(models.Model):
    tg_id = models.IntegerField()
    tg_first_name = models.CharField(max_length=500, blank=True, null=True)
    tg_username = models.CharField(max_length=500, blank=True, null=True)
    name = models.CharField(max_length=500, blank=True, null=True)
    choosen_lang = models.CharField(max_length=50, blank=True, null=True)
    phone_number = models.CharField(max_length=50, blank=True, null=True)
    status = models.CharField(max_length=500, blank=True, null=True)
    is_operator = models.BooleanField(default=False, blank=True, null=True)
    is_supervisor = models.BooleanField(default=False, blank=True, null=True)
    is_available = models.BooleanField(default=True, blank=True, null=True)

    def __str__(self):
        return self.tg_first_name
    
class Chat(models.Model):
    is_closed = models.BooleanField(default=False)
    client = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='client_of_chat', blank=True, null=True) 
    operator = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='operator_of_chat', blank=True, null=True)
    created = models.DateTimeField(auto_now_add=True)

class Appeal(models.Model):
    custom_user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, blank=True, null=True)
    body = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.body

buttons.py

from main.models import *

lang_btn = [
    ["O'zbek"],
    ["Русский"],
]

main_page_btn_uz = [
    ["Savol"],
    ["Shikoyat"],
]

main_page_btn_ru = [
    ["Вопрос"],
    ["Жалоба"],
]

chat_btn_uz = [
    ['Onlayn opertor'],
]

chat_btn_ru = [
    ['Онлайн-оператор'],
]

main_btn_operator = [
    ["Открытие чаты"],
    ["Закрытие чаты"],
]

close_chat_btn_ru = [
    ['✅Завершить чат'],
]

close_chat_btn_uz = [
    ['✅Chatni yopish'],
]

bot.py

Я тестировал с одним оператором и одним клиентом все работает нормально, но когда я добавляю еще одного оператора и больше клиентов сообщения между ними путаются... Что я делаю не так, пожалуйста, помогите мне?!)

Это действительно сводится к глобальным переменным и полному отсутствию безопасности параллелизма. В PTB есть встроенное решение для хранения подобных переменных. Я бы, вероятно, использовал bot_data для хранения в этом случае, но вам следует прочесть всю статью и самостоятельно выбрать лучший вариант.

Чтобы управлять доступностью операторов и обеспечить правильную маршрутизацию сообщений между клиентами и операторами, необходимо правильно обрабатывать флаг is_available. Также необходимо убедиться, что каждый чат правильно сопоставлен с соответствующим оператором и клиентом. Я внес несколько изменений в код ur 2 для управления статусом is_available операторов:

  1. Обновите функцию move_to_get_reply, чтобы помечать оператора как занятого, когда он начинает чат
  2. .
async def move_to_get_reply(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    print('move_to_get_reply()')
    global ch_id
    query = update.callback_query
    await query.answer()

    if ch_id is not None:
        created_chat = Chat.objects.get(id=ch_id)
        operator = CustomUser.objects.get(tg_id=query.from_user.id)
        created_chat.operator = operator
        created_chat.save()

        # Mark operator as busy
        operator.is_available = False
        operator.save()

        await context.bot.send_message(created_chat.operator.tg_id, 'Чат открыт, напишите ...', reply_markup=markup_close_chat_ru)

        if created_chat.client.choosen_lang == "O'zbek":
            await context.bot.send_message(created_chat.client.tg_id, 'Assalomu alaykum, nima yordam bera olaman?', reply_markup=markup_close_chat_uz)
        else:
            await context.bot.send_message(created_chat.client.tg_id, 'Здравствуйте, чем могу помочь?', reply_markup=markup_close_chat_ru)

        return PHASE_OPERATOR_CHAT
    else:
        await context.bot.send_message(created_chat.operator.tg_id, 'Клиент уже завершил чат.', reply_markup=markup_main_operator)
        return PHASE_MAIN_OPERATOR
  1. Обновление функции done_operator оператора 2 mark как доступной, когда чат заканчивается
async def done_operator(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    print('done_operator()')
    global ch_id
    await update.message.reply_text("Вы завершили чат.", reply_markup=markup_main_operator)

    try:
        chat = Chat.objects.get(id=ch_id)
    except Chat.DoesNotExist:
        print('Chat not found')
        return PHASE_MAIN_OPERATOR

    chat.is_closed = True
    chat.save()

    client_tg_id = chat.client.tg_id
    await context.bot.send_message(client_tg_id, 'Оператор завершил чат', reply_markup=markup_main_ru)

    # Mark operator as available
    operator = chat.operator
    operator.is_available = True
    operator.save()

    # Reset the ch_id after chat is done
    ch_id = None

    return PHASE_MAIN_OPERATOR
  1. Обновление функции done_client аналогичным образом для обработки завершения чата клиентом
async def done_client(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    print('done_client()')
    global ch_id
    await update.message.reply_text("Вы завершили чат.", reply_markup=markup_main_ru)

    try:
        chat = Chat.objects.get(id=ch_id)
    except Chat.DoesNotExist:
        await update.message.reply_text("Chat not found.")
        print('Chat not found')
        return PHASE_MAIN_PAGE_RU

    chat.is_closed = True
    chat.save()

    if chat.operator:
        await context.bot.send_message(chat.operator.tg_id, 'Клиент завершил чат', reply_markup=markup_main_operator)
        # Mark operator as available
        operator = chat.operator
        operator.is_available = True
        operator.save()

    # Reset the ch_id after chat is done
    ch_id = None

    return PHASE_MAIN_PAGE_RU
  1. Убедитесь, что функция lets_chat назначает чату доступного оператора и правильно обрабатывает несколько операторов
async def lets_chat(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    print('lets_chat()')
    global ch_id

    created_chat = Chat.objects.create(
        is_closed=False,
        client=CustomUser.objects.get(tg_id=update.message.from_user.id),
    )
    
    ch_id = created_chat.id
    keyboard = [
        [
            InlineKeyboardButton("Присоединиться к чату", callback_data=ch_id),
        ],
    ]
    reply_markup = InlineKeyboardMarkup(keyboard)
    
    available_operator = CustomUser.objects.filter(is_operator=True, is_available=True).first()

    notification_message = (
        f"<u>Появился новый чат!</u>\n"
        f'\n'
        f'<b>Имя пользователя:</b> <i>{update.message.from_user.first_name}</i>'
        f'\n'
        f'<b>Обрашения:</b> <i>{update.message.text}</i>' 
    )
    if available_operator:
        await context.bot.send_message(available_operator.tg_id, notification_message, reply_markup=reply_markup, parse_mode='HTML')

    if created_chat.client.choosen_lang == "O'zbek":
        await update.message.reply_text("Iltimos, kutib turing! Biz sizni mavjud operator bilan bog'layabmiz.", reply_markup=markup_close_chat_uz)
    else:
        await update.message.reply_text("Пожалуйста, ожидайте! Мы соединяем Вас со свободным оператором.", reply_markup=markup_close_chat_ru)
    
    return PHASE_CLIENT_CHAT

Я надеюсь, что эти изменения обеспечат, чтобы оператор отмечался как занятой (is_available=False), когда он начинает чат, и как доступный (is_available=True), когда чат заканчивается. Это должно решить проблему путаницы сообщений между разными клиентами и операторами.

Вернуться на верх