Arrow
2 августа 2019 г. 17:06

Деплой Telegram бота на Django с исползованием webhook

Bot, Django, Telegram

Добрый день.

Помогите пожалуйста никак не могу найти информации о деплое Telegram бота на pythonenywhere.

Написал бота используя pyTelegramBotAPI (база данных, админка и еще некоторые вещи на Django). Столкнулся с проблемой, что на локальной машине с использованием polling() все работает, а когда выкладываю на pythonenywhere, чтобы не мучать сервер нужно использовать webhook. Только нигде в интернете нет вменяемого объяснения как сделать это для Django. Для Flask куча описаний. Пытался переделать то, что удалось найти, но бот никак не заводится. Вернее само приложение Django работает прекрасно, как и админка бота, только бот ника не реагирует на мои сообщения в телеграм. По логам все ок и теграм говорит, что webhook установлен успешно.

Вот мой код:

Файл "urls.py" проекта:

  1. urlpatterns = [
  2. path('admin/', admin.site.urls),
  3. path('', include('bot.urls')),
  4. ..................
  5. ]

Файл "urls.py" бота:

  1. urlpatterns = [
  2. path('', UpdateBot.as_view(), name='update'),
  3. ]
  4.  

Файл "views.py" бота:

  1. from telebot import TeleBot, types
  2. from rest_framework.response import Response
  3. from rest_framework.views import APIView
  4. from TelegramBot.settings import TOKEN
  5.  
  6.  
  7. bot = TeleBot(TOKEN)
  8.  
  9.  
  10. class UpdateBot(APIView):
  11. def post(self, request):
  12. # Сюда должны получать сообщения от телеграм и далее обрабатываться ботом
  13. json_str = request.body.decode('UTF-8')
  14. update = types.Update.de_json(json_str)
  15. bot.process_new_updates([update])
  16.  
  17. return Response({'code': 200})
  18.  
  19.  
  20. @bot.message_handler(commands=['start'])
  21. def start_message(message):
  22. # User написал /start в диалоге с ботом
  23. text = '<b>Настройка бота!</b>\n\n'
  24. text += 'Чтобы пначать использовать бата и настроить его по Вашим предпочтениям ответьте на следующие вопросы.\n\n'
  25. text += '......................'
  26.  
  27. keyboard = types.InlineKeyboardMarkup()
  28. key_begin = types.InlineKeyboardButton(text='🖊️ Начать', callback_data='begin')
  29. keyboard.add(key_begin)
  30.  
  31. bot.send_message(message.chat.id, text=text, reply_markup=keyboard, parse_mode='HTML')
  32.  
  33. ..............................
  34.  
  35. # Webhook
  36. bot.set_webhook(url="https://myapp.pythonanywhere.com/" + TOKEN)

Для Flask (с ним не работал) есть такой пример:

  1. import telebot
  2. import os
  3. from flask import Flask, request
  4.  
  5. bot = telebot.TeleBot(токен вашего бота)
  6. server = Flask(__name__)
  7.  
  8.  
  9. @bot.message_handler(commands=['start'])
  10. def start(message):
  11. bot.reply_to(message, 'Hello, ' + message.from_user.first_name)
  12.  
  13. @bot.message_handler(func=lambda message: True, content_types=['text'])
  14. def echo_message(message):
  15. bot.reply_to(message, message.text)
  16.  
  17.  
  18. @server.route("/токен бота", methods=['POST'])
  19. def getMessage():
  20. bot.process_new_updates([telebot.types.Update.de_json(request.stream.read().decode("utf-8"))])
  21. return "!", 200
  22.  
  23. @server.route("/")
  24. def webhook():
  25. bot.remove_webhook()
  26. bot.set_webhook(url="https://ссылка на приложение/токен вашего бота")
  27. return "!", 200
  28.  
  29. server.run(host="0.0.0.0", port=os.environ.get('PORT', 5000))

Буду благодарен за помощь.

3

Вам это нравится? Поделитесь в социальных сетях!

11
nayk1982
  • 3 августа 2019 г. 18:25

Добрый день.
Посмотрите для начала статус установленного WebHook, нет ли ошибок с сертификатом безопасности. Это можно сделать из командной строки:

  1. curl https://api.telegram.org/bot<TOKEN>/getWebhookInfo
    Arrow
    • 4 августа 2019 г. 19:06

    Добрый день.

    Попробовал реализовать через сертефикат и изменил код:

    1. from telebot import TeleBot, types
    2. from os.path import join
    3. from django.conf import settings
    4. from rest_framework.response import Response
    5. from rest_framework.views import APIView
    6. from TelegramBot.settings import TOKEN
    7.  
    8.  
    9. bot = TeleBot(settings.TOKEN, threaded=False)
    10.  
    11.  
    12. class UpdateBot(APIView):
    13. def post(self, request):
    14. # Сюда должны получать сообщения от телеграм и далее обрабатываться ботом
    15. json_str = request.body.decode('UTF-8')
    16. update = types.Update.de_json(json_str)
    17. bot.process_new_updates([update])
    18.  
    19. return Response({'code': 200})
    20.  
    21.  
    22. @bot.message_handler(commands=['start'])
    23. def start_message(message):
    24. # User написал /start в диалоге с ботом
    25. text = '<b>Настройка бота!</b>\n\n'
    26. text += 'Чтобы пначать использовать бата и настроить его по Вашим предпочтениям ответьте на следующие вопросы.\n\n'
    27. text += '......................'
    28.  
    29. keyboard = types.InlineKeyboardMarkup()
    30. key_begin = types.InlineKeyboardButton(text='🖊️ Начать', callback_data='begin')
    31. keyboard.add(key_begin)
    32.  
    33. bot.send_message(message.chat.id, text=text, reply_markup=keyboard, parse_mode='HTML')
    34.  
    35. ..............................
    36.  
    37. # Webhook
    38. CERTIFICATE = join(settings.BASE_DIR, 'PUBLIC.pem')
    39. bot.set_webhook(url="https://myapp.pythonanywhere.com/", certificate=open(CERTIFICATE),
    40. max_connections=100000)

    Выполнил команду:

    1. curl https://api.telegram.org/bot<TOKEN>/getWebhookInfo

    Результат:

    1. {"ok":true,"result":{"url":"https://myapp.pythonanywhere.com/","has_custom_certificate":true,"pending_update_count":6,"last_error_date":1564923410,"last_error_message":"SSL error {error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed}","max_connections":100}}
      Arrow
      • 4 августа 2019 г. 20:04

      В логах pythonenywhere:

      1. Request: method=get url=https://api.telegram.org/bot<KEY>/setWebhook params={'url': 'https://myapp.pythonanywhere.com/', 'max_connections': 100000} files={'certificate': <_io.TextIOWrapper name='/home/admin/Bot/PUBLIC.pem' mode='r' encoding='UTF-8'>}
      2. (apihelper.py:55 uWSGIWorker1Core0) DEBUG - TeleBot: "The server returned: 'b'{"ok":true,"result":true,"description":"Webhook was set"}''"
      3. The server returned: 'b'{"ok":true,"result":true,"description":"Webhook was set"}'

      Проверка:

      1. curl https://api.telegram.org/bot<KEY>/getWebhookInfo
      2. {"ok":true,"result":{"url":"https://myapp.pythonanywhere.com/","has_custom_certificate":true,"pending_update_count":7,"last_error_date":1564927364,"last_error_message":"SSL error {error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed}","max_connections":100}}
        nayk1982
        • 4 августа 2019 г. 21:31

        Сертификат не проходит проверку. У меня такое было, когда пытался использовать самоподписанный сертификат. Решилось установкой сертификата Lets Encrypt. После этого WebHook заработал без проблем и сообщения стали приходить.

          Arrow
          • 4 августа 2019 г. 23:07
          • (ред.)

          Попробовал создать сертефикат при помощи acme-tiny (Let's Encrypt certificates):

          1. openssl genrsa 4096 > account.key

          В коде изменил:

          1. # Webhook
          2. CERTIFICATE = join(settings.BASE_DIR, 'account.key')
          3. bot.set_webhook(url="https://myapp.pythonanywhere.com/", certificate=open(CERTIFICATE),
          4. max_connections=100000)

          И получил в логах:

          1. Request: method=get url=https://api.telegram.org/bot<KEY>/setWebhook params={'url': 'https://myapp.pythonanywhere.com/', 'max_connections': 100000} files={'certificate': <_io.TextIOWrapper name='/home/admin/Bot/account.key' mode='r' encoding='UTF-8'>}
          2. The server returned: 'b'{"ok":false,"error_code":400,"description":"Bad Request: bad webhook: Failed to set custom certificate file"}''

          Сервер его вообще не принимает. C рекомендуемым certbot то же самое.

            Arrow
            • 5 августа 2019 г. 18:16

            Решил уйти от сертификата к простому токену.

            Изменил код на:

            Файл urls.py приложения бота:

            1. from django.urls import path
            2. from bot.views import UpdateBot
            3. from django.conf import settings
            4. from django.views.decorators.csrf import csrf_exempt
            5.  
            6.  
            7. urlpatterns = [
            8. path('{}'.format(settings.TOKEN), csrf_exempt(UpdateBot.as_view()), name='update'),
            9. ]
            10.  

            Код views.py приложения бота:

            1. from rest_framework.response import Response
            2. from django.http import HttpResponse
            3. from django.views import View
            4. from telebot import TeleBot, types, logger
            5. from bot.models import User, Category, Content, Country, Link, User_category, User_content, SentLinks
            6.  
            7.  
            8. bot = TeleBot(settings.TOKEN, threaded=False)
            9.  
            10.  
            11. class UpdateBot(View):
            12. def get(self, request, *args, **kwargs):
            13. return HttpResponse("Бот запусчен и работает.")
            14.  
            15. def post(self, request, *args, **kwargs):
            16. json_str = request.body.decode('UTF-8')
            17. update = types.Update.de_json(json_str)
            18. bot.process_new_updates([update])
            19.  
            20. return Response({'code': 200})
            21.  
            22.  
            23. @bot.message_handler(commands=['start'])
            24. def start_message(message):
            25. # User написал /start в диалоге с ботом
            26. text = '<b>Настройка бота!</b>\n\n'
            27. text += 'Чтобы пначать использовать бата и настроить его по Вашим предпочтениям ответьте на следующие вопросы.\n\n'
            28. text += '......................'
            29.  
            30. keyboard = types.InlineKeyboardMarkup()
            31. key_begin = types.InlineKeyboardButton(text='🖊️ Начать', callback_data='begin')
            32. keyboard.add(key_begin)
            33.  
            34. bot.send_message(message.chat.id, text=text, reply_markup=keyboard, parse_mode='HTML')
            35.  
            36. ..............................
            37.  
            38. # Webhook
            39. bot.remove_webhook()
            40. bot.set_webhook(url=f"{settings.DOMAIN}/{settings.TOKEN}")
            41. # DOMAIN = "https://myapp.pythonanywhere.com"
            42. #

            Сразу, когда перезагрузил веб приложение в pythonenywhere пришло куча сообщений от бота (реакция на мои сообщения /start), а затем опять тишина.

            При проверке через терминал получаю:

            1. $ curl https://api.telegram.org/bot<KEY>/getWebhookInfo
            2. {"ok":true,"result":{"url":"https://myapp.pythonanywhere.com/<KEY>","has_custom_certificate":false,"pending_update_count":19,"last_error_date":1565006606,"last_err
            3. or_message":"Wrong response from the webhook: 500 Internal Server Error","max_connections":40}}

            В логах тишина. Немогу понять, что не верно. Особенно если по началу все вроде бы заработало.

              Arrow
              • 5 августа 2019 г. 19:44

              Все заработало.

              Только теперь не знаю как быть. Толи я, что-то не то сделал, то ли телеграм тупит.
              Постоянно сробатывает Одна и таже процедура в коде, а именно часть 'Рассказать о боте':

              1. def menu(message):
              2. try:
              3. if message.text == 'Рассказать о боте':
              4. """ Эта часть отрабатывает """
              5. markup = types.InlineKeyboardMarkup()
              6. forward_btn = types.InlineKeyboardButton(
              7. text='...............',
              8. url='https://t.me/share/url?url=https://t.me/' + settings.BOT_NAME)
              9. markup.add(forward_btn)
              10. text = '.............. '
              11. bot.send_message(message.chat.id, text=text,
              12. reply_markup=markup, parse_mode='html')
              13. bot.register_next_step_handler(message, menu)
              14.  
              15. elif message.text == 'Поиск':
              16. bot.delete_message(message.chat.id, message.message_id)
              17. markup = types.InlineKeyboardMarkup()
              18. search_channel = types.InlineKeyboardButton(
              19. text='.....', callback_data='.....')
              20. search_bot = types.InlineKeyboardButton(
              21. text='....', callback_data='.....')
              22. search_chat = types.InlineKeyboardButton(
              23. text='....', callback_data='....')
              24. markup.row(search_channel)
              25. markup.row(search_bot)
              26. markup.row(search_chat)
              27. text = '.......'
              28. bot.send_message(message.chat.id, text=text,
              29. parse_mode='html', reply_markup=markup)
              30. bot.register_next_step_handler(message, owner_menu)
              31.  
              32. elif message.text == 'Изменить настройки':
              33. bot.register_next_step_handler(message, owner_menu)
              34. manage_interests(message)
              35.  
              36.  
              37. else:
              38. raise Exception()
              39. except:
              40. bot.register_next_step_handler(message, owner_menu)

              Подскажите пожалуйста, где я туплю. Спасибо.

                nayk1982
                • 5 августа 2019 г. 22:05

                Возможно телеграм повторяет одно и то же сообщение, это бывает, если в ответ на сообщение от телеграм ваш бот ничего не отвечает. Нужно на сообщения посылать ответ:

                1. {"ok": true}

                И еще, в каждом сообщении есть счетчик update_id, для каждого чата. Можно его запоминать и сравнивать, чтоб не дублировать ответы.

                  Arrow
                  • 6 августа 2019 г. 17:07
                  • Ответ был помечен как решение.

                  Спасибо.

                  Что-то не особо помогает.

                  Похоже что у pythonanywhere какие-то проблемы с передачей обновлений боту или у телеграм. То приходят данные, то нет. Сейчас проверка показывает:

                  1. {"ok":true,"result":{"url":"https://myapp.pythonanywhere.com/<Key>","has_custom_certificate":false,"pending_update_count":0,"max_connections":40}}

                  А сообщения вообще перестали приходить.

                  Можете привести пример реализации ответа {"ok": true} или подредактировать мое, может я уже туплю на ровном месте.

                  Мой код:

                  urls.py приложения бота:

                  1. from django.urls import path
                  2. from django.conf import settings
                  3. from django.views.decorators.csrf import csrf_exempt
                  4. from bot.views import UpdateBot, Check
                  5.  
                  6. app_name = 'PlayMarketBot'
                  7.  
                  8. urlpatterns = [
                  9. path('{}'.format(settings.TOKEN), csrf_exempt(UpdateBot.as_view()), name='update'),
                  10. path('/', csrf_exempt(Check.as_view()), name='check'),
                  11. ]

                  views.py приложения бота:

                  1. from django.conf import settings
                  2. from random import randint
                  3. from django.http import HttpResponse
                  4. from django.views import View
                  5. from telebot import TeleBot, types, logger
                  6. from bot.models import User, Category, Content, Country, Link, User_category, User_content, SentLinks
                  7. from threading import Timer
                  8. import logging
                  9.  
                  10. logger.setLevel(logging.DEBUG)
                  11.  
                  12. __author__ = '@masterarrow'
                  13.  
                  14. bot = TeleBot(settings.TOKEN)
                  15.  
                  16. update_id = None
                  17.  
                  18.  
                  19. class Check(View):
                  20. def get(self, request, *args, **kwargs):
                  21. return HttpResponse("Бот запусчен и работает.")
                  22.  
                  23.  
                  24. class UpdateBot(View):
                  25. def post(self, request, *args, **kwargs):
                  26. global update_id
                  27. json_str = request.body.decode('UTF-8')
                  28. update = types.Update.de_json(json_str)
                  29. if update_id != update.update_id:
                  30. bot.process_new_updates([update])
                  31. update_id = update.update_id
                  32.  
                  33. return Response(b'{"ok":true,"result":[]}')
                  34.  
                  35.  
                  36. @bot.message_handler(commands=['start'])
                  37. def start_message(message):
                  38. bot.delete_message(message.chat.id, message.message_id)
                  39. text = '/////////////////////////'
                  40.  
                  41. keyboard = types.InlineKeyboardMarkup()
                  42. key_begin = types.InlineKeyboardButton(text='🖊️ Начать', callback_data='begin')
                  43. keyboard.add(key_begin)
                  44.  
                  45. bot.send_message(message.chat.id, text=text, reply_markup=keyboard, parse_mode='HTML')
                  46.  
                  47. .............................................
                  48.  
                  49. def menu(message):
                  50. try:
                  51. # Always working ???
                  52. if message.text == 'Рассказать':
                  53. markup = types.InlineKeyboardMarkup()
                  54. forward_btn = types.InlineKeyboardButton(
                  55. text='Пригласить',
                  56. url='https://t.me/share/url?url=https://t.me/' + settings.BOT_NAME)
                  57. markup.add(forward_btn)
                  58. text = '......................................'
                  59. bot.send_message(message.chat.id, text=text,
                  60. reply_markup=markup, parse_mode='html')
                  61. bot.register_next_step_handler(message, menu)
                  62.  
                  63. elif message.text == 'Поиск':
                  64. bot.delete_message(message.chat.id, message.message_id)
                  65. markup = types.InlineKeyboardMarkup()
                  66. search_channel = types.InlineKeyboardButton(
                  67. text='......', callback_data='.............')
                  68. search_bot = types.InlineKeyboardButton(
                  69. text='.......', callback_data='.........')
                  70. search_chat = types.InlineKeyboardButton(
                  71. text='.....', callback_data='...........')
                  72. markup.row(search_channel)
                  73. markup.row(search_bot)
                  74. markup.row(search_chat)
                  75. text = '....................'
                  76. bot.send_message(message.chat.id, text=text,
                  77. parse_mode='html', reply_markup=markup)
                  78. bot.register_next_step_handler(message, menu)
                  79.  
                  80. elif message.text == 'Настроить':
                  81. manage_interests(message)
                  82. bot.register_next_step_handler(message, menu)
                  83.  
                  84. else:
                  85. raise Exception()
                  86. except:
                  87. bot.register_next_step_handler(message, menu)
                  88.  
                  89.  
                  90. bot.remove_webhook()
                  91. bot.set_webhook(url=f"{settings.DOMAIN}/{settings.TOKEN}")
                  92.  

                  Большое спасибо за помощь.

                    nayk1982
                    • 7 августа 2019 г. 8:04

                    На python не делал, поэтому не подскажу, как правильно.
                    Бросаются в глаза последние строчки из views.py:

                    1. bot.remove_webhook()
                    2. bot.set_webhook(url=f"{settings.DOMAIN}/{settings.TOKEN}")

                    Они что, выполняются каждый раз? Если да, то это не правильно. WebHook устанавливается один раз и после этого ничего с ним делать не нужно (пока адрес вашего сервера не изменится или токен бота). Я бы вообще управление вэбхуком из кода убрал, это можно из консоли сделать одной строчкой, когда понадобится.

                    Могу рассказать, как делал я своего бота. На сервере для начала создал простой скрипт для приема сообщений и вывода в лог-файлы всего, что принял. Это поможет разобраться, как работает Telegram API. WebHook поставил на этот скрипт, добавил бота в тестовый чат Telegram и просто изучал, как работает API, параллельно читая документацию. Потом начал потихоньку наполнять этот скрипт и делать дополнительные если нужно. По началу лучше логировать все, что только можно, легче разобраться.

                      Arrow
                      • 7 августа 2019 г. 12:24

                      Спасибо за подсказку.
                      Да я и не подумал, что код установки webhook можно вообще убрать или вынести.

                      Большое спасибо за помощь.

                        Комментарии

                        Только авторизованные пользователи могут публиковать комментарии.
                        Пожалуйста, авторизуйтесь или зарегистрируйтесь
                        • Последние комментарии
                        • IscanderChe
                          12 апреля 2025 г. 17:12
                          Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
                        • AK
                          1 апреля 2025 г. 11:41
                          Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
                        • Evgenii Legotckoi
                          9 марта 2025 г. 21:02
                          К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
                        • VP
                          9 марта 2025 г. 16:14
                          Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
                        • ИМ
                          22 ноября 2024 г. 21:51
                          Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…