Русский
Русский
English
Статистика
Реклама

Telegram bots

Botsman новая платформа для разработки Telegram-ботов

30.12.2020 12:20:04 | Автор: admin

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

Для тех же, кому уже захотелось ознакомиться с Botsman (но не очень хочется много читать) вот ссылка, милости прошу: https://bots.mn/. Главное, о чём стоит помнить платформа только-только запустилась, и (пока что) не стоит переносить на неё что-то серьёзное и масштабное.

Предыстория: путь к созданию Ботсмана

Пять лет назад в Telegram появилась возможность создавать ботов автоматизированные аккаунты, которыми можно управлять с помощью своих скриптов. Меня это сразу заинтересовало: я люблю делать маленькие, но полезные утилитки.

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

Поэтому почти сразу пришла в голову идея сделать такую платформу, которая возьмёт весь мониторинг на себя: будет логировать и входящие обновления, и то, как бот на них реагирует (и какие возникают при этом ошибки). Заодно можно и графики удобные строить.

Но, к сожалению, дальше придумывания красивого названия Botsman и покупки короткого домена bots.mn дело поначалу не зашло. Проект оказался непривычно большим для того, чтобы осилить его в одиночку, и за пять лет я то брался за него, то снова откладывал на потом.

И только к концу злополучного 2020-го я нашёл в себе силы довести его до рабочего состояния, которое можно показать широкой публике. Специально поставил цель зарелизиться до Нового года: чтобы добавить этому году плюс одно хорошее событие :)

Итак, что же сейчас предлагает данная платформа?

Проксирование запросов

Во-первых, мой сервис умеет работать как прокси: вы подключаете бота к Botsman, он ловит все приходящие от Telegram события, логирует, и передаёт вашему серверу. Это удобно, если у вас уже написан и где-то крутится бот: ваш код может даже не подозревать про эту прослойку.

Выбор способа обработки запросов ботомВыбор способа обработки запросов ботом

У этой фичи есть очевидный недостаток: небольшое увеличение времени отклика (поскольку в цепочке Telegram ваш сервер появляется дополнительное звено).

Но этой осенью Telegram сделал крутую вещь: они выложили в открытый доступ код сервера-посредника Bot API. По своей сути это такое приложение, которое внутри общается с Телеграмом как клиент по их протоколу MTProto, а снаружи у него торчит уже простое и понятное Bot API. И когда вы обращаетесь к публичному Bot API по HTTPS запрос на самом деле идёт к инстансу такого сервера, а теперь стало можно поднять его самому. И конечно же, внутри Ботсмана я так и сделал (и это новшество оказалось ещё одним мотиватором закончить проект).

Таким образом, вашего бота можно настроить так, что цепочка не станет длиннее: вместо
Telegram сервер Bot API ваш сервер будет
Telegram Botsman ваш сервер.

Правда, тут уже потребуются правки в коде вашего бота: исходящие запросы придётся делать не на api.telegram.org, а на api.bots.mn/telegram. Зато Botsman сможет логировать и их тоже!

Собственно, поговорим о логировании:

Живая лента обновлений

После настройки бота в Botsman, можно сразу открыть страницу Events, отправить что-то в Телеграме своему боту, и увидеть, как это сообщение появилось на экране в реальном времени. Если у вас настроен прокси вы увидите и результат перенаправления запроса вашему серверу. Если ваш сервер шлёт запросы через проксирующий эндпоинт api.bots.mn/telegram они тоже туда попадут.

Так в интерфейсе Botsman выглядит лог всех происходящих с ботом событийТак в интерфейсе Botsman выглядит лог всех происходящих с ботом событий

С технической стороны, конечно, тут всё просто: я использую веб-сокеты чтобы проталкивать все обновления на клиент, если у вас открыта соответствующая вкладка (если открытых страниц нет, лишней нагрузки это создавать не должно). Возможно, если бы я начинал этот проект сейчас, я бы попробовал использовать Server-sent events вместо веб-сокетов, но вряд ли есть какой-то повод переходить на них.

Кроме бесконечного лога, куда все события сыпятся вперемешку, Botsman ещё умеет показывать интерфейс, похожий на обычный список диалогов в самом мессенджере то, как их видит ваш бот. При желании можно даже никак не обрабатывать запросы, а просто вручную открывать диалоги и отвечать от имени бота (этакий режим техподдержки возможно, впоследствии я добавлю поддержку управления ботами с нескольких аккаунтов с разными ролями, если это действительно будет актуально).

Слежение за показателями бота

Ну и конечно же, статистика и графики, куда без них. Честно говоря, аналитик из меня так себе, поэтому сейчас Botsman показывает только довольно базовые метрики общее число апдейтов, число чатов, число пользователей, дневную и месячную аудиторию (DAU и MAU). Графики по числу апдейтов на каждый день/час, и по среднему времени обработки запросов. Было бы, конечно, интересно смотреть на всякую демографию, но в Telegram в этом плане мало информации о пользователях.

Графики в разделе Stats. Как видно, через одного из моих ботов уже прошло почти 20 млн апдейтов.Графики в разделе Stats. Как видно, через одного из моих ботов уже прошло почти 20 млн апдейтов.

Для подсчёта статистики я с самого начала выбрал старый добрый Redis, а вот на клиентской стороне решил рисовать графики с помощью библиотеки, которую до этого сам же написал на конкурс того же Телеграма. Не пропадать же добру :)

Скриптинг

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

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

Поэтому я выбрал JavaScript: моя изначальная идея была взять встроенную в Node песочницу, немного доработать (как это сделано в библиотеках Sandcastle или vm2), чтобы сделать её безопаснее, и выполнять код ботов в ней.

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

В итоге я обратил внимание на библиотеку isolated-vm: она тоже реализует песочницу в JS, но делает это другим, более безопасным (и, что важно, многопоточным) образом. По сути это обёртка над присутствующим в V8 механизмом изолятов независимых контекстов, которые ничего не знают друг про друга. Эта же библиотека, кстати, используется в игре Screeps, где игрокам тоже нужно писать своих ботов.

Что особенно хорошо для ботов: состояние одного такого изолята можно держать в памяти как кусок данных (не расходуя процессорное время), и только когда боту понадобится обработать прилетевший апдейт от Телеграма оживлять это состояние, вызывая в нём функцию-обработчик. И конечно можно ограничить как потребляемую память, так и процессорное время (с этим, помнится, в нодовской песочнице были некоторые сложности).

Скриптинг: внутреннее API, обработчики событий

Дальше одним из камней преткновения было дать удобное внутреннее API для написания ботов. Конечно, можно было сказать вашему коду будет доступна переменная update и метод callMethod, а дальше делайте, что хотите. Но раз уж я проектирую всю песочницу, нужно идти до конца.

В частности, мне хотелось реализовать механизм состояний: часто боты рассчитаны на пошаговый диалог, и это надо учесть, как-то запоминать контекст, даже если пользователь бросил диалог и вернулся к нему на следующий день. В итоге я решил, что для каждого чата, пользователя или сообщения я позволю хранить объект с произвольными полями. А у чатов (как личных, так и групповых) в придачу есть понятие пути, route оно позволяет раскидать обработчики событий по тому, на каком шаге находится пользователь.

Само добавление обработчиков делается довольно просто:

on(ctx => {  ctx.log('Some update received: ', update);});

Это самый универсальный обработчик: он вызывается при любом событии (напомню, кроме события пришло сообщение Telegram присылает и ряд других оповещений).

Перед функцией-коллбэком можно указать один конкретный тип события: тогда обработчик будет вызываться исключительно для него.

on('message', ctx => {  ctx.message.reply('Hi!');});

А что будет, если объявить два обработчика и они оба подходят для текущего апдейта? Botsman вызовет только первый из них но можно передать управление следующему, если вернуть false (ну или промис, резолвящийся в false разумеется, всё делалось с расчётом на асинхронный код).

Ещё есть удобные способы обработывать только текстовые сообщения с помощью on.text (их можно заодно ещё и фильтровать по регэкспу), только команды с помощью on.command, инлайн-запросы on.inline, и коллбэк-запросы (нажатия на кнопки под отправленными сообщениями) on.callback. О них можно почитать в документации.

В примерах выше видно, что обработчику передаётся единственный параметр экземпляр класса EventContext, и через него делаются все вызовы. Это позволяет Ботсману связывать, например, вызовы API или возникающие ошибки с конкретным апдейтом, который к ним привёл.

Ну а как разграничить обработчики для разных состояний (путей) чата? Для этого предназначена глобальная функция state:

state('step1', (on, state) => {  // Этот обработчик вызовется для любого сообщения,  // если наш чат в состоянии 'step1' - и переведёт его  // в состояние 'step2'  on.text(ctx => {    ctx.route.to('step2');   });});state('step2', (on, state) => {  // А этот обработчик вызывается, если наш чат в  // состоянии 'step2' и возвращает его в 'step1'  on.text(ctx => {    ctx.route.to('step1');   });});

Обратите внимание: функция state немедленно вызывает переданный ей коллбэк с двумя аргументами, которые заменяют собой глобальные функции on и state. Добавленный с помощью локальной функции on обработчик будет вызываться только в указанном состоянии, а с помощью локальной функции state можно создавать вложенные состояния (хотя их можно создать и вручную, просто записывая путь, разделённый слэшами: 'step1/substep1/branchA'). Пока что, впрочем, иерархическая структура состояний особых преимуществ по сравнению с линейной не имеет (но может помочь их логически упорядочить).

Скриптинг: форматируем сообщения с помощью tagged template literals

Отдельно поделюсь одной, казалось бы, незначительной, но весьма радующей лично меня деталью. Если вы уже пробовали использовать Telegram API, то возможно сталкивались со сложностями при отправке текста с форматированием особенно когда в него нужно подставить пользовательские данные. Telegram умеет принимать и HTML, и Markdown, но и в том, и в другом случае подставляемые данные нужно обрабатывать, эскейпить управляющие символы, что не очень удобно.

К счастью, не так давно при отправке сообщения (и в других методах, где можно отправлять форматированный текст) появилась возможность просто указать отдельно, какие участки в нём нужно отформатировать. Добавляем к этому tagged templates из ES6 и получаем вот что:

await ctx.call('sendMessage', {  chat_id: 12345,  ...fmt`Hello ${fmt.bold(foo)}! You can combine ${fmt.italic(bar).bold()} styles together.Links are supported ${fmt.text_link(linkLabel, linkUrl)}.`,});

Выглядит немного непривычно, зато а) не нужно ничего эскейпить, б) невозможно сломать вёрстку, потеряв какой-нибудь HTML-тэг или символ разметки Markdown. Если у вас валидный JS будет и валидная вёрстка. Под капотом запись fmt`something` возвращает объект с двумя полями text и entities поэтому его нужно распаковывать с помощью ... (spread syntax). Ну или его можно передать напрямую в короткие методы типа ctx.message.reply(fmt`something`) или ctx.chat.say(fmt`something`).

Мне кажется, что у tagged template literals вообще не очень много уместных применений в реальном мире, но тут у меня получилось найти одно из них :)

Скриптинг: код по расписанию и запросы к внешним API

Должен сделать важную оговорку: так как код выполняется в изолированных контекстах, у скриптов нет ни доступа к API самой Node, ни возможности импортировать внешние модули. Однако я реализовал метод fetch (по аналогии с одноимённым браузерным API) он позволяет делать не слишком тяжёлые запросы к внешним серверам. Кроме того, доступна глобальная функция cron с помощью неё можно запланировать регулярное выполнение повторяющихся действий:

cron('0 0 * * FRI', ctx => {  ctx.log('This function should execute each Friday at midnight');});

Первый параметр тут либо привычный формат crontab, либо интервал в секундах (правда, и то, и другое с разрешением не чаще раза в пять минут). Я думаю, что возможности выполнять код по расписанию и делать запросы к внешним API позволят реализовать множество практических задач. И не исключено, что в будущем список возможностей (например, импорт каких-то проверенных модулей) будет расширяться.

Скриптинг: веб-интерфейс

Писать код прямо в браузере не самое комфортное занятие, но я стремлюсь сделать его хотя бы сравнимым с разработкой в IDE. Сейчас в качестве редактора я использую CodeMirror (в первую очередь из-за его относительно небольшого веса), в дальнейшем, вероятно, добавлю возможность переключиться на Monaco он ощутимо тяжелее, зато должен быть шустрее и функциональнее (именно он используется внутри VS Code).

Так сейчас выглядит редактор кода в BotsmanТак сейчас выглядит редактор кода в Botsman

Пока что в Botsman нет разбивки кода на отдельные файлы в этом тоже может быть неудобство, если вы собираетесь писать сложного бота. Зато код сохраняется автоматически, и есть возможность хранить все его предыдущие версии это ещё не Git, но всё же.

И ещё одна небольшая, но довольно ценная возможность: проверка кода перед деплоем с помощью тестов. При этом Botsman по нажатию одной кнопки сразу покажет подробный лог выполнения (см. скриншот выше): время инициализации, обработки запроса, отладочный вывод и возникшие ошибки (код с синтаксическими ошибками задеплоить не выйдет в принципе).

Песочница для запросов к Telegram

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

Панель вызова методов Telegram APIПанель вызова методов Telegram API

Будущие планы

Как было сказано ранее, Botsman находится в самом начале своего пути. Возможно, его даже настигнет Хабраэффект (надеюсь, что нет!). Возможно, ему станет тяжко, если созданные на нём боты наберут популярность не исключено, что тогда придётся вводить платные возможности. Поскольку занимаюсь им я сейчас в одиночку, сложно сказать, что с ним будет.

В очень примерных планах сейчас такие фичи:

  • Визуальный конструктор в дополнение к скриптингу

  • Глобальное key-value хранилище + создание собственных хранилищ

  • Поддержка других платформ, кроме Telegram

  • Доступный снаружи эндпоинт для вызова кода бота

  • Управление ботом с нескольких аккаунтов

  • Навигация по коду, разбивка на модули, поддержка сторонних модулей

  • Более гибкое тестирование кода, автоматические тесты

  • Больше статистики и графиков

  • Оповещения (если с ботом что-то не так)

  • Улучшение вида чатов

  • Улучшение работы с файлами (скачивание, загрузка), в том числе в песочнице

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

Подробнее..

Простой Telegram-бот для получения информации через MQTT

04.05.2021 06:11:43 | Автор: admin

Этот бот был разработан для просмотра информации, находящейся на mqtt сервере внутри локальной сети. Он может работать на одном компьютере с mqtt сервером (в том числе на Raspberry PI или подобном) или отдельно. Задача удалённого управления не ставилась, только предоставление доступа к данным.

Протокол MQTT предназначен специально для использования в различных устройствах автоматики, на нём очень легко организовать телеметрию и сбор данных. Этот протокол поддерживают как "умные" бытовые устройства, так и многие промышленные контроллеры. Также есть множество проектов на ESP8266, ESP32 или подобных платформах.

На mqtt сервере публикуются данные телеметрии с различных датчиков - допустим, это метеостанция и термометры в теплицах. Для их просмотра на десктопе я раньше делал виджет, веб-страницу, потом захотелось иметь эти данные всегда под рукой. Конечно, можно было пробросить доступ к серверу наружу или разместить его в облаке, но тут возникает ещё целый ряд проблем. Тема использования бота в мессенджере для меня не новая - ещё пятнадцать лет назад я использовал ICQ клиента на мобильном телефоне, чтобы с помощью ICQRemote и скрипта на AutoIt переключать треки в Winamp на десктопе. Сейчас средний телефон гораздо мощнее того десктопа и имеет почти столько же постоянной памяти, но проблема внешнего подключения к устройству в локальной сети по-прежнему существует. И боты всё так же отлично справляются с решением этой проблемы. В общем, было решено делать Телеграм-бота, который просто предоставляет по запросу необходимую информацию.

Я не буду рассказывать про регистрацию имени бота и получение токена, так как это всё уже есть в каждой предыдущей статье про телеграм-ботов. Перейду сразу к программе на Python. Она разрабатывалась под Windows, но я не вижу препятствий для её запуска под другими системами - используемые библиотеки python-telegram-bot и paho-mqtt это позволяют.

Настройки программы хранятся в ini файле. В секции TELEGRAM прописывается токен для бота, в секции MQTT адрес и логин/пароль mqtt сервера, а также топик для получения данных (если несколько - через запятую, без пробелов). При запуске бот подключается к mqtt серверу и подписывается на необходимые топики. Глубина вложенности уровней может быть любой. Поступающие данные попадают в словарь alldata, ключом является полный топик:

{'greenhouse/1/temp': '24.76','greenhouse/1/upd': '22.04 18:20:30','greenhouse/2/temp': '22.95','greenhouse/3/temp': '28.91','air/outdoor/1/temp': '17.32','air/outdoor/1/upd': '22.04 18:21:25','air/outdoor/1/pressure': '739','air/outdoor/1/humidity': '58.3'}

При необходимости чтения из этого словаря формируется разбитый на уровни вложенный словарь tree - дерево данных. Это преобразование выполняет функция maketree.

def maketree(group, items, path):    def sep(s):        return s.split('/', 1)    head = [i for i in items if len(sep(i)) == 2]    tail = [i for i in items if len(sep(i)) == 1]    if len(tail) == 1:        return group, tail[0]    gv = groupby(sorted(head), lambda i: sep(i)[0])    return group, dict([(i, path) for i in tail] + [maketree(g, [sep(i)[1] for i in v], '') for g, v in gv])

В результате получается такая структура:

{    "air": {        "outdoor": {            "1": {                "humidity": "58.3",                "pressure": "739",                "temp": "17.32",                "upd": "22.04 18:21:25"            }        }    },    "greenhouse": {        "1": {            "temp": "24.76",            "upd": "22.04 18:20:30"        },        "2": {            "temp": "22.95"        },        "3": {            "temp": "28.91"        }    }}

Из такого словаря очень легко получить нужные данные. Например, температура в 1 теплице находится по адресу tree[greenhouse][1][temp]. Так как данные обновляются намного чаще, чем запрашиваются, преобразование в момент запроса достаточно эффективно. При более частых запросах лучше будет формировать и обновлять такое дерево сразу при поступлении данных.

Затем идёт подключение к серверу Телеграм. Бот предназначен для работы в локальной сети без возможности пробросить необходимый для веб-хука порт, поэтому для подключения он использует Long Polling запросы. Используется библиотека python-telegram-bot версии 12.8, так как в 13 версии разработчики что-то поломали. Установить её можно командой pip3 install python-telegram-bot==12.8

Для упрощения работы с ботом не используются команды: в ответ на знакомое слово он отсылает информацию, на незнакомое выдаёт кнопки выбора. В моём случае кнопок две, они прописаны в функции get_keyb:

def get_keyb():    return [[InlineKeyboardButton('Погода', callback_data='1'),            InlineKeyboardButton('Теплицы', callback_data='2')]] 

А также в словаре, строчными буквами для удобства сравнения:

keys = {'погода': '1', 'теплицы': '2', 'приборы': '3'}

Ключ "приборы" добавлен для отладки, на него ответ всегда "40"

Вариант диалогаВариант диалога

Кнопки можно многократно использовать для обновления информации.

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

Подробнее..

Бот в телеграм, озвучивающий ваши эмоции в сообщении

29.08.2020 12:21:18 | Автор: admin

Привет!


В этой статье я опишу своего бота в телеграм, который работает и сейчас.


Для чего бот создавался, его цель и умения


В реальности человек использует множество звуков для выражения своих эмоций и отношений. Но почти все системы искусственного интеллекта и голосовые помощники "слишком интеллектуальные". Они просто отбрасывают эмоциональные звуки, не понимают и не могут правильно использовать междометия. Поэтому я создал бота, и он открывает возможность естественной речи, полной мычанием, скрипами, всхлипываниями, рыками и сотней других эмоциональных тонов и полутонов.
Предпологаю, что разрабатываемый алгоритм будет работать в коммерческом секторе, например, отслеживая эмоциональное состояние клиента и направлять ветвление алгоритма чтобы предупредить его возможные действия. Эмоции первые регуляторы поведения и по тону речи или, например, по хмыканью легко отследить замешательство и раздражение пользователя и среагировать до того как он начнёт ругаться и требовать.
Можно так же придать и самим голосовым помощникам больше "человечности". Они могут откашливаться перед длинной лекцией или усмехаться вместо того, чтобы стандартным голосом объяснять что сейчас была шутка.
В медицинской практике программа поможет определить состояние пациента, который не может членораздельно говорить и по отдельным всхлипываниям сориентировать персонал на процедуру лечения. Она может применяться и для отдельных категорий заболеваний, таких как аутизм и дислексия.
В мессенджерах бот поможет передать чувства и отношения через сеть наравне, в дополнении или вместо стандартных эмоджи.
Как площадку для экспериментов и разработки я использую мессенджер "Телеграм".
На текущем моменте бот @YouToneBot возвращает звук эмоции на стандартный смайлик.
В дальнейшем планируется научить бота совершать обратную операцию, то есть на звуковой тон выдавать "эмодзи".


Какой он в итоге получится?


Он сможет отправлять голосовые сообщение, озвучивающие смайл, так же он сможет это делать прямо в чате! Даже не придётся переносить сообщение, достаточно будет написать его имя в сообщении, и написать свое сообщение.


image
image
image

Код


Итак, сейчас бот умеет выдавать голосовое сообщение, на смайл. Впрочем, ничего сложного, начнем.
Я написал бота на Python, используя библиотеку PyTelegramBotApi.
Сначала нужно узнать, как послать голосовое сообщение от бота. Я узнал, что бот может посылать те голосовые сообщения, которые были посланы ему, Т.К. у PyTelegramBotApi есть функция send_voice() используя id голосовго сообщения, его можно получить просто у функции message_handler() взять message.voice.file_id.
Теперь нужно придумать как хранить звуки к смайликам, и вот как я придумал: хранить в JSON , как ключ будет смайлик к которому бот будет отправлять звук, а как значение id голосового сообщения.
Соответственно, нам нужно послать голосовое сообщение боту, и записать его id, к смайлику. Вот и сложилось представление о базе данных звуков, нам всего лишь нужен "python словарь", и в него нужно записовать как ключ смайл, а как значение id голосового сообщения.
Вот примерно так:


{    "emoji1": "voice_id1",    "emoji2": "voice_id2",    "emoji3": "voice_id3",    "emoji4": "voice_id4",    "emoji5": "voice_id5",    "emoji6": "voice_id6",}#почему-то не отображаются смайлики, поэтому "emoji" == ~смайлик

Вот именно так у меня устроена база данных звуков.


Теперь код.
Для начала я сделал класс YouTone(), чтобы писать все методы бота там.
В def init(self) я прописал TOKEN, VOICE_SOUNDS
TOKEN Это просто токен бота
VOICE_SOUNDS это словарь, из смайлов и id голосовых сообщений#база данных
И там же, в init, создаю переменную self.BOT, вот таким кодом


TL.TeleBot(self.TOKEN)

Это собственно, сам бот.
Итак, в классе сразу написал 3 функции, это
LS_handler() отвечающая за личную переписку с ботом
start_handler() отвечающая за команды боту (/start и /get) в личной переписке
local_lerning() для записи голосовых сообщений к смайлам
В LS_handler() пока сделаем echo бота


LS_handler()
def LS_handler(self):    @self.BOT.message_handler(content_types=['text'])    def send_text(message):        def msg(message_text):            self.BOT.send_message(message.chat.id, str(message_text))        msg(message.text)

В start_handler() пока просто на команду /start бот будет отвечать привет


start_handler()
def start_handler(self):    @self.BOT.message_handler(commands=['start', "get"])    def commands(message):        if message.text == "/start":            self.BOT.send_message(message.chat.id, 'Привет. Я высылаю тебе звуки по смайлам')

И теперь займемся local_lerning(). Нам нужно записать к смайлам звуки охарактеризовающие смайл, для этого нужно послать голосовое сообщение боту, принять его на стороне бота, и записать к соответствующему смайлу.
Сделал я это так чтобы было удобо озвучивать, используя tkinter. На окне в tkitner показывается какой смайл нужно озвучить, посылается звук боту, на окне tkitner появляется кнопка "прослушить звук", я прослушиваю и если мне он нравится то нажимаю на "сохранить звук к смайлу", и к словарю базы данных дописывается (или перезаписывается):
смайл как ключ
id голосового сообщения как значение


Для этой функции я сделал 3 функции, и также базу данных просто смайликов без значений
window_smile() само окно tkitner
bot_work() это запуск работы бота, с возможностю приема голосовых сообщений.
save_sound() для сохранения в файл смайла и id голосового сообщения
База данных смайликов она нужна будет для создание базы данных смайликов со значениями


Итак, window_smile() это просто функция где рисуется и запускается окно


window_smile()
def window_smile():#эта функция принадлежит функции local_lerning()    self.root = Tk()    self.root.geometry("500x500")    self.smile_tkinter = Label(text=self.AUDIO_SOUNDS_ITEMS[self.index][0],font='Times 30')    self.open_sound = Button(text="Открыть звук",font='Times 10',command=lambda: webbrowser.open(url=r"путь/до/файла/в/котором/звук.ogg"))    self.Y_or_N = Button(text="Принять звук",font='Times 15',command=save_sound)    self.info = Label(text="""\n\n\n\n\n\n\nСмотришь на смайл, озвучиваешь,\n проверяешь,\n нажимаешь 'Принять звук',\n смайл меняется,\n ты его озвучивашь,\n проверяешь,\n нажимаешь принять звук.""",font="Consolas 11")    self.smile_tkinter.pack()    self.open_sound.pack()    self.Y_or_N.pack()    self.info.pack()    self.root.mainloop() 

bot_work() эта функция запускает бота, отслеживает входящие голосовые сообщения, и записывает их в "путь/до/файла/в/котором/звук.ogg",


bot_work()
def bot_work():    @self.BOT.message_handler(content_types=['text',"voice"])    def send_text(message):        def msg(message_text):            self.BOT.send_message(message.chat.id, str(message_text))        self.smile_now = self.SOUNDS_DB[self.index][0]        self.smile_tkinter.config(text=self.smile_now)        try:            self.id_voice = message.voice.file_id            self.voice_info = self.BOT.get_file(file_id=self.id_voice)            self.voice_file = requests.get('https://api.telegram.org/file/bot{0}/{1}'.format(self.TOKEN, self.voice_info.file_path)).content        except BaseException as e:            print(e)        else:            with open("путь/до/файла/в/котором/звук.ogg", "wb") as f:                f.write(self.voice_file)

Тут мы с помощю get_file(), поулчаем id файла (не голосового сообщения), с помощю id получаем сам файл через requests, и записываем его.
Также получаем id голосовго сообщения, и записываем его в self.voice_id.


save_sound()
def save_sound():    self.voices_good.update({self.smile_now:str(self.id_voice)})    self.index += 1    self.smile_now = self.SMILES_DB[self.index][0]    self.smile_tkinter.config(text=self.smile_now)    with open("data.txt","w",encoding="utf-8") as f:        f.write(str(self.voices_good))

Эта функция вызывается по кнопке "принять звук".
Здесь мы обновляем словарь смайликов и голосовых сообщений новым смайлом и голосовым сообщением.
Также тут есть перменная о которой я не сказал, это self.index, в этой перменной хранится индекс смайлика который нужно озвучить на данный момент, Т.Е. индекс в списке self.SMILES_DB


Дальше я в функции local_lerning(), запускаю 2 функции в разных потоках, это
window_smile()и bot_work().
Делаю это я с помощю threading.
Вот так


from threading import Threadth1 = Thread(target=window_smile)th1.start()th2 = Thread(target=bot_work)th2.start()

local_lerning()
def local_lerning(self):    from threading import Thread    self.smile_now = None    self.index = 0    self.id_voice = None    self.voices_good = {    }    self.smile_tkinter = None    def save_sound():        self.voices_good.update({self.smile_now:str(self.id_voice)})        self.index += 1        self.smile_now = self.AUDIO_SOUNDS_ITEMS[self.index][0]        self.smile_tkinter.config(text=self.smile_now)        with open("data.txt","w",encoding="utf-8") as f:            f.write(str(self.voices_good))    def window_smile():        self.root = Tk()        self.root.geometry("500x500")        self.smile_tkinter = Label(text=self.AUDIO_SOUNDS_ITEMS[self.index][0],font='Times 30')        self.open_sound = Button(text="Открыть звук",font='Times 10',command=lambda: webbrowser.open(url=r"C:\Program Files\JetBrains\projects\telegram\voice.ogg"))        self.Y_or_N = Button(text="Принять звук",font='Times 15',command=save_sound)        self.info = Label(text="""\n\n\n\n\n\n\nСмотришь на смайл, озвучиваешь,\n проверяешь,\n нажимаешь 'Принять звук',\n смайл меняется,\n ты его озвучивашь,\n проверяешь,\n нажимаешь принять звук.""",font="Consolas 11")        self.smile_tkinter.pack()        self.open_sound.pack()        self.Y_or_N.pack()        self.info.pack()        self.root.mainloop()    def bot_work():        @self.BOT.message_handler(content_types=['text',"voice"])        def send_text(message):            def msg(message_text):                self.BOT.send_message(message.chat.id, str(message_text))            def snd_doc(name_doc):                self.BOT.send_document(message.chat.id, open(name_doc, "rb"))            self.smile_now = self.AUDIO_SOUNDS_ITEMS[self.index][0]            self.smile_tkinter.config(text=self.smile_now)            try:                self.id_voice = message.voice.file_id                self.voice_info = self.BOT.get_file(file_id=self.id_voice)                self.voice_file = requests.get('https://api.telegram.org/file/bot{0}/{1}'.format(self.TOKEN, self.voice_info.file_path)).content            except BaseException as e:                print("ошибка: ",e)            else:                with open("voice.ogg", "wb") as f:                    f.write(self.voice_file)    th1 = Thread(target=window_smile)    th1.start()    th2 = Thread(target=bot_work)    th2.start()

Вот так выглядит озвучивание смаликов.
image


Итак, с помощю этой функции можно записать голосовые сообщения к смайлам. Так я и сделал.
В итоге у меня появилась база данных смайлов, и голосовых сообщений к ник.


Теперь нужно релизовать логику отправления голосовых сообщений при виде смайлов. Начнем сначала с личных сообщений, потом будет делать бота inline.
Для того чтобы обноружить смайлы в сообщении, я сделал простую функцию, разбивания сообщения на список, а потом проверяя нету ли в базе данных звуков и смайлов такого символа, если есть, то тогда звук на смайл найден.
Вот так она выгдялит


message_list = list(message.text)is_send = Falsefor word in message_list:    if word in self.VOICE_SOUNDS:        if self.VOICE_SOUNDS[word]:            snd_voice(voice_id=self.VOICE_SOUNDS[word])            print("smile has been found")            is_send = True            breakif not is_send:    print("smile has been not found")

LS_handler()
def LS_handler(self):    @self.BOT.message_handler(content_types=['text'])    def send_text(message):        def msg(message_text):            self.BOT.send_message(message.chat.id, str(message_text))        def snd_doc(name_doc):            self.BOT.send_document(message.chat.id, open(name_doc, "rb"))        def snd_voice(voice_id: str):            self.BOT.send_voice(message.chat.id,voice=voice_id)        message_list = list(message.text)        is_send = False        for word in message_list:            if word in self.VOICE_SOUNDS:                if self.VOICE_SOUNDS[word]:                    snd_voice(voice_id=self.VOICE_SOUNDS[word])                    is_send = True                    break        if not is_send:            msg("Я не нашел знакомого мне смайла 

Вот собственно и весь LS_handler(), теперь почти тоже самое, только в inline_handler()


как работает бот inline

Бот inline работатет так что его нужно вызвать, просто написав его имя, и дальше написать ему сообщение, которое он прочитает и уже выдаст вам какой-то результат


inline_handler()
def inline_handler(self):    @self.BOT.inline_handler(lambda query: len(query.query) > 0)    def query_text(query):        message_list = list(query.query)        #ставлю сразу что не нашел, а если найду, то поменяю ;)        output_msg = [types.InlineQueryResultArticle(            id="1",            title="Я не нашел знакомого мне смайла ",            input_message_content=types.InputTextMessageContent(message_text="Я не нашел знакомого мне смайла")        )]        is_send = False        id_now = 1#id для сообщений        for word in message_list:            try:                self.VOICE_SOUNDS[word]            except KeyError:                pass            else:                if self.VOICE_SOUNDS[word]:                    if is_send == False:                        output_msg = []                    is_send = True                    if not word in [i.title for i in output_msg]:#если смайла нет в списке уже выданных смайлов, то надо добавить ;)                        output_msg.append(types.InlineQueryResultCachedVoice(                            id=str(id_now),                            voice_file_id=self.VOICE_SOUNDS[word],                            title=str(word),                            caption=query.query                        ))                    else:                        pass#типа если есть уже такой смайл то добавлять второй такой жене надо                    id_now +=1 #это для того чтобы id сообщений увеличивался        self.BOT.answer_inline_query(query.id, output_msg)

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


И вот так сейчас уже можно использовать бота в inline


image

Заключение


В этой статье я рассказал больше про логику, нежели про код, почти как и в прошлой статье.
Бота @YouToneBot я еще буду развивать, хочу сделать чтобы пользователь мог записать голосовое сообщение, а бот выдать по нему смайл, Т.Е. наоборот нынешней работе бота.
Спасибо!

Подробнее..

Из песочницы Бот Telegram для пиццерии на Python с помощью telebot

23.06.2020 12:07:45 | Автор: admin

Вступление


На Хабре уже есть статья о Telegram боте, написанном на Python с помощью telebot. Признаться, свое знакомство с чат-ботами в недавно разблокированном мессенджере я начинал с этой статьи. Моя писанина это дополнение, включающее в себя работу с Inline кнопками и базой данных.


Установка библиотеки


Telebot библиотека для взаимодействия с Telegram API, которая привлекла меня простотой, поэтому я считаю, что для новичков она подходит на все 100%.


Чтобы установить библиотеку, введите в терминале следующую команду.


pip install pytelegrambotapi


Получение API-токена


Чтобы создать бота, обратимся к "отцу" всех ботов @BotFather. Отправьте команду /newbot, после чего введите имя бота, затем алиас, оканчивающийся на bot.


image

Пишем код


Лучше записывать API-токены, номера кошелька и тому подобное в отдельный файл, поэтому создадим файл config.py:


TOKEN = "1176585885:AAH-RA2kZym9E5tR8JFLtYYjNxrMHnsJr0o"

Основной код находится в bot.py


# импорт библиотекиimport telebot# Подтягиваем токен бота и все необходимое для получение средств за пиццуfrom config import TOKEN# Создание ботаbot = telebot.TeleBot(TOKEN)# Декоратор для обработки всех текстовых сообщений@bot.message_handler(content_types=['text'])def all_messages(msg):    # Получаем сообщение пользователя    message = msg.text    # Получаем Telegram id пользователя (очевидно, для каждого пользователя он свой)    user_id = msg.chat.id    # Отправляем сообщение    bot.send_message(user_id, f"Вы написали: {message}")# Запускаем бота, чтобы работал 24/7if __name__ == '__main__':    bot.polling(none_stop=True)

Мы создали эхо-бота. Подумаем, что будет в нашем боте:


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

База данных


Я пользуюсь SQLiteStudio для создания баз данных.


Структура таблицы для пользователя



  • id уникальный идентификатор пользователя. Таким выступает id пользователя в мессенджере, поэтому без зазрения совести будем использовать его.
  • stat статус работы с ботом. Запоминаем на каком шаге остановился пользователь.
  • ord. Здесь мы в виде строки храним заказ пользователя. Пока я не представляю как сделать по-другому, поэтому предлагайте идеи в комментариях.
  • random_code. В будущем будем генерировать рандомный код для оплаты через QIWI.
  • time. Пользователь выбирает, когда хочет получить заказ, а мы сохраняем в это поле.

Структура таблицы для пиццы



Данные по пиццам:



По name получаем описание пиццы и её стоимость.Осталось добавить фотографию. Создадим директорию data, поместим в нее img, а уже там будут храниться изображения.



Классы для взаимодействия с базой данных


Так, базу данных создали, теперь приступим к классам. Создадим файл user.py, в нём класс User. Сначала напишем пару функций для подключения к базе данных и закрытию соединения.


# Подключаем библиотекуimport sqlite3class User:    # Функция для соединения с БД    def connect_to_db(self):        self.connect = sqlite3.connect('PizzaApplication.db')        self.cursor = self.connect.cursor()    # Закрываем соединение с БД    def close(self):        self.connect.close()

Бота открыл пользователь, которого мы не знаем. Поэтому нужны функции добавления нового юзера и получения всех пользователей. Второе нужно для того, чтобы проверять наличие каждого написавшего "/start" в БД.


# Получаем id всех пользователей, позже будем проверять наличие пользователя в этом списке    def get_all_id(self):        self.connect_to_db()        request = "SELECT id FROM user"        result = self.cursor.execute(request).fetchall()        self.close()        return [i[0] for i in result]    # Добавляем нового пользователя    def add_id_to_db(self, user_id):        self.connect_to_db()        request = "INSERT INTO user(id, stat) VALUES(?, ?)"        self.cursor.execute(request, (user_id, "start"))        self.connect.commit()        self.close()

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


    # Получаем заданное поле по пользователю    def get_field(self, user_id, field):        self.connect_to_db()        request = f"SELECT {field} FROM user WHERE id=?"        result = self.cursor.execute(request, (user_id,)).fetchone()        self.close()        return result[0]    # Меняем значение поля    def set_field(self, user_id, field, value):        self.connect_to_db()        request = f"UPDATE user SET {field}=? WHERE id=?"        self.cursor.execute(request, (value, user_id))        self.connect.commit()        self.close()

Основные функции для пользователя написаны. Позже будем возвращаться к этому классу, а пока приступим к пицце. Создадим файл pizza.py, в нём класс Pizza. Начальные функции такие же, как у User.


import sqlite3class Pizza:    # Подключение к базе данных    def connect_to_db(self):        self.connect = sqlite3.connect('PizzaApplication.db')        self.cursor = self.connect.cursor()    # Закрытие базы данных    def close(self):        self.connect.close()

Нужно получать данные из БД.


    def get_field(self, pizza_name, field):        self.connect_to_db()        request = f"SELECT {field} FROM pizza WHERE name=?"        result = self.cursor.execute(request, (pizza_name,)).fetchone()        self.close()        return result[0]

Для админ-панели потребуется функция set_field (подобная есть в классе User), но её я пока не предусмотрел.


В следующей статье разберём клавиатуры и начнём писать код в bot.py. Спасибо за внимание!

Подробнее..

Внедряем оплату BTC куда угодно (Python)

05.11.2020 18:21:17 | Автор: admin

Предыстория

Полгода назад взялся за один проект с возможностью оплаты биткойном. Так как проект делали на языке python, то и оплату хотелось реализовать на нем же. Сразу же взялся анализировать готовые решения, доступные библиотеки и Rest API Blockchain.com. С апи блокчейна я моментально обломался, так как их токен для использования апи довольно не просто получить.

Затем решил юзать различные библиотеки (block-io, bitcoinlib, blockchain и др.) После пару ночей попыток реализовать нормальную оплату, остановился на bitcoinlib, так как она более менее стабильно работала, и я спокойно переводил с одного кошелька на другой. Беда наступила когда появились первые 100 пользователей и вся оплата внезапно рухнула. Возможно я криво написал или что-то не так понял с работой библиотеки, но любые попытки восстановить работу оплаты были безуспешны, только если обнулять бдшку, но и так неизвестно сколько бы она продержалась.

В итоге решили оставить без BTC оплаты. Я опечалился и не связывался с оплатой биткойном полгода.

К чему я пришел

На днях я все-таки решил добить этот вопрос для себя, надеюсь кому-то еще пригодятся мои наработки.

Все начинается с seed фразы. Мнемоническаяфраза(англ. Mnemonic phrase илиSeed фраза) - это список слов, которые хранят всю информацию, необходимую для восстановления биткоин-кошелька. Существуют несколько стандартов генерации фраз BIP 32, BIP 39, BIP 44, и еще BIP 49. Самый распространенный - это BIP 44 (12 слов).

Пример seed фразы:

vivid area able second bicycle advance demand alpha flip stable drift route

Чтобы сгенерировать фразу будем использовать библиотеку bipwallet. Чтобы ее установить воспользуемся командой pip install bipwallet.

from bipwallet import wallet# generate 12 word mnemonic seedseed = wallet.generate_mnemonic()print(seed)

Если мы хотим получить напрямую доступ к биткойн кошельку, то, зная фразу, можем сразу восставновить кошелек в blockhain.com:

https://login.blockchain.com/#/recover

Следующим шагом мы будем преобразовывать нашу seed фразу до получения нужного адреса кошелька биткойна.

Чтобы во всем не запутаться и знать какие данные мы должны получить, я использовал сайт https://iancoleman.io/bip39/

Генерация дочернего адреса кошелька для каждого пользователя:

Чтобы получить наш нулевой адрес Биткойн кошелька на основе seed фразы (12VeK1eRgPHRUikNLXq3Nuz99gS2S46QMD), нам нужно пройти всю цепочку преобразований. Методом проб и ошибок мне все-таки удалось получить адрес кошелька следующим кодом:

from bipwallet.utils import *def gen_address(index):    # Наша seed фраза    seed = 'vivid area able second bicycle advance demand alpha flip stable drift route'    # Мастер ключ из seed фразы    master_key = HDPrivateKey.master_key_from_mnemonic(seed)    # Public key из мастер ключа по пути 'm/44/0/0/0'    root_keys = HDKey.from_path(master_key, "m/44'/0'/0'/0")[-1].public_key.to_b58check()    # Extended public key    xpublic_key = str(root_keys, encoding="utf-8")    # Адрес дочернего кошелька в зависимости от значения index    address = Wallet.deserialize(xpublic_key, network='BTC').get_child(index, is_prime=False).to_address()    rootkeys_wif = HDKey.from_path(master_key, f"m/44'/0'/0'/0/{index}")[-1]    # Extended private key    xprivatekey = str(rootkeys_wif.to_b58check(), encoding="utf-8")    # Wallet import format    wif = Wallet.deserialize(xprivatekey, network='BTC').export_to_wif()    return address, str(wif, 'utf-8')print(gen_address(0))

Данная функция возвращает адрес кошелька и wif в зависимости номера. Максимальное число с которым удалось получить адрес это 999999999.

wif (Wallet import format) - это просто кодирование байтов ключа в кодировку Base58 + контрольная сумма. Он нам понадобится в дальнейшем при генерации транзакции.

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

Проверка баланса и транзакции:

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

import requests# Адрес кошелька пользователя wallet = '12VeK1eRgPHRUikNLXq3Nuz99gS2S46QMD'# wallet = gen_address(0)url = f'https://blockchain.info/rawaddr/{wallet}'x = requests.get(url)wallet = x.json()print('Итоговый баланс:'+str(wallet['final_balance']))print('Транзакции:'+str(wallet['txs']))if wallet['total_received']==0:  print('баланс пустой')

Вот таким простым кодом мы можем получить всю информацию по балансу и транзакциях пользователя. Дальше все зависит от логики самого приложения.

Транзакции

На данном этапе мы дали каждому пользователю свой адрес кошелька и знаем все транзакции с данным адресом, но этого недостаточно. Нам нужно чтобы мы могли отправить его же деньги обратно. Для этого воспользуемся библотекой bit. Чтобы ее установить воспользуемся командой pip install bit.

from bit import PrivateKey# Приватный ключ из wifmy_key = PrivateKey(wif='L46ixenNSu8Bqk899ZrH8Y96t8DHqJ1ZyxzQBGFTbh38rLHLaPoY')# Количество долларов перевода, можно поменять на btcmoney=0.1# Кошелек куда будут переведены деньгиwallet='17ya3bCpPioyPH8kAyFkEDBUqdjF6wwPxo'# Коммисия перевода, если поставить слишком маленькую, то транзакцию не примут# И чем больше коммисия, тем быстрее пройдет переводfee=2000# Генерация транзакцииtx_hash = my_key.create_transaction([(wallet, money, 'usd')],fee=fee,absolute_fee=True)print(tx_hash)

В итоге мы получили вот такую транзакцию:

0100000001fe64490fce5e85d5eb00865663a3d44f4108549fdb2840b086cfc781390d4a2d010000006a47304402202dc1496d28bb10d50d94d70870e2a79ea472c5960de8f7418bb30f9b96643efc02204691547c98edad3181a056bf6404601efe289200ba8e3073a2f5b7c0c7f4fec10121026516c551584b484ce3ca7bb71bbf24cce133bf40bdf4e2ce5a3936bc7e66a2abffffffff02e3020000000000001976a9144c83a20250ccb62ce2b3b1ea80c6082b634fdf9f88ac08f40200000000001976a9144c83a20250ccb62ce2b3b1ea80c6082b634fdf9f88ac00000000

Выглядит красиво, но что с этим делать?

Можно зайти на сайт https://www.blockchain.com/btc/pushtx

и вручную отправить эту транзакцию.

Также можем декодировать эту транзакцию и проверить все ли верно мы указали https://www.blockchain.com/btc/decode-tx

Но нам нужно это автоматизировать, поэтому напишем несколько строк:

import requestsurl = 'https://blockchain.info/pushtx'tx='0100000001fe64490fce5e85d5eb00865663a3d44f4108549fdb2840b086cfc781390d4a2d010000006a47304402202dc1496d28bb10d50d94d70870e2a79ea472c5960de8f7418bb30f9b96643efc02204691547c98edad3181a056bf6404601efe289200ba8e3073a2f5b7c0c7f4fec10121026516c551584b484ce3ca7bb71bbf24cce133bf40bdf4e2ce5a3936bc7e66a2abffffffff02e3020000000000001976a9144c83a20250ccb62ce2b3b1ea80c6082b634fdf9f88ac08f40200000000001976a9144c83a20250ccb62ce2b3b1ea80c6082b634fdf9f88ac00000000'x = requests.post(url, data = {'tx':tx})result = x.textprint(result)

Выполним пост запрос, если получаем ответ: Transaction Submitted. Это значит, что через несколько секунд транзакция появится в сети и деньги спишутся с пользователя.

Применение

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

Для демонстрации работы BTC оплаты, я напишу простенького телеграм бота, который будет выполнять роль клиента Blockchain.com, то есть вы сможете хранить в нем свои биткойны и от туда же переводить другим людям. Ссылка на исходники бота будут в конце.

Проверить работу бота можно тут: https://t.me/Blockchain_client_bot

Задеплоил на heroku, так что надеюсь не будет падать)

Функционал бота

Регистрация пользователя

В качестве БД я использовал sqlite3 и создал одну таблицу пользователей:

import sqlite3conn = sqlite3.connect("my.db")  # или :memory: чтобы сохранить в RAMcursor = conn.cursor()cursor.execute("CREATE TABLE users (chatid INTEGER , name TEXT, balance INTEGER, btc_wallet TEXT, wif TEXT, btc_sent TEXT, state INTEGER)")conn.commit()

При нажатии start мы регистрируем пользователя, генерируем для него адрес биткойн кошелька, wif и добавляем данные в БД:

sql = "SELECT COUNT(*) FROM users "cursor.execute(sql)user = cursor.fetchone()  address, wif= gen_address(user[0]+1)sql_insert = "INSERT INTO users VALUES ({}, '{}', 0,'{}','{}','no',0)".format(message.chat.id,                                                                           message.chat.first_name,address,wif)cursor.execute(sql_insert)conn.commit()

Проверка баланса

if message.text == '? Ваш баланс':  url = f'https://blockchain.info/rawaddr/{data[3]}'  x = requests.get(url)  wallet = x.json()  await bot.send_message(message.chat.id, f'''? *Итоговый баланс:* {format(wallet['final_balance'] / 100000000, '.9f')} BTC*Всего получено:* {format(wallet['total_received'] / 100000000, '.9f')} BTC*Всего отправлено:* {format(wallet['total_sent'] / 100000000, '.9f')} BTChttps://www.blockchain.com/ru/btc/address/{data[3]}''', parse_mode= "Markdown")

Получить BTC

Для создания qr-кода я использовал библиотеку qrcode и на вход передал ранее сгенерированный адрес биткойн кошелька из БД.

 if message.text == '? Получить BTC':    img = qrcode.make(data[3])    img.save('qr.jpg')    await bot.send_message(message.chat.id, f'''? Ваш адрес биткойн кошелька:*{data[3]}*''', parse_mode= "Markdown")        await bot.send_photo(message.chat.id,photo=open('qr.jpg', 'rb'))

Отправить BTC

try:    sum = float(message.text)    url = f'https://blockchain.info/rawaddr/{data[3]}'    x = requests.get(url)    wallet = x.json()    if sum + 10000 <= wallet['final_balance'] / 100000000:        try:            my_key = PrivateKey(wif=data[4])            # Коммисия перевода, если поставить слишком маленькую, то транзакцию не примут            # И чем больше коммисия, тем быстрее пройдет перевод            fee = 10000            # Генерация транзакции            tx_hash = my_key.create_transaction([(data[5], sum, 'btc')], fee=fee, absolute_fee=True)            print(tx_hash)            url = 'https://blockchain.info/pushtx'            x = requests.post(url, data={'tx': tx_hash})            result = x.text            sql = "UPDATE users SET state = {} WHERE chatid = {}".format(0, message.chat.id)            cursor.execute(sql)            conn.commit()            await bot.send_message(message.chat.id, result)        except Exception:            await bot.send_message(message.chat.id, " Ошибка при выолнении транзакции")    else:        await bot.send_message(message.chat.id, '  На вашем балансе недостаточно средств.')except ValueError:    await bot.send_message(message.chat.id, 'Неправильно введена сумма отправления, попробуйте еще раз')

Проверим через сайт, что транзакция отправилась:

Исходники и как запустить

Скачать исходники бота можно тут: github.com/Lil-hack/blockchain-client

Склонировав репозиторий, устанавливаем необходимые пакеты:

pip install-rrequirements.txt

Некоторые библиотеки у меня не заработали на windows, так что лучше сразу запускать на linux.

В файле main.py заменяем ваш токен телеграм бота:

# Ваш токен от BotFatherTOKEN = 'YOUR TOKEN'

В файле btc_core.py заменяем на вашу seed фразу:

# Ваша seed фразаseed = 'YOUR SEED'

И запускаем бота командой: python main.py

Работает на python 3.7.0 и выше. Бот написан за один вечер, так что просьба строго не судить ^^

Итого

Как оказалось, все довольно не сложно, и в несколько десятков строк можно добавить оплату BTC в любой python проект. Я не профи в криптографии, так что скорее всего многие моменты упустил, но надеюсь кому-то эта статья будет полезна.

Подробнее..

Telegram-бот на Java для самых маленьких от старта до бесплатного размещения на heroku

25.11.2020 10:14:05 | Автор: admin


Для кого написано


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

Предыстория


Когда моя дочь начала изучать арифметику, я между делом накидал алгоритм генерации простых примеров на сложение и вычитание вида 5 + 7 =, чтобы не придумывать и не гуглить для неё задания.

И тут на глаза попалась новость, что Telegram выпустил новую версию Bot API 5.0. Ботов я раньше не писал, и потому решил попробовать поднять бота как интерфейс для своей поделки. Все примеры, которые мне удалось найти, показались либо совсем простыми (нужные мне функции не были представлены), либо очень сложными для новичка. Также мне не хватало объяснений, почему выбран тот или иной путь. В общем, написано было сразу для умных, а не для меня. Потому я решил описать свой опыт создания простого бота надеюсь, кому-нибудь это поможет быстрее въехать в тему.

Что в статье есть, чего нет


В статье есть про:

  • создание бекенда не-инлайн бота на Java 11 с использованием Telegram Bot Api 5.0;
  • обработка команд вида /dosomething;
  • обработка текстовых сообщений, не являющихся командами (т.е. не начинающихся с "/");
  • отправку пользователю текстовых сообщений и файлов;
  • деплой и запуск бота на heroku.

В статье нет про:

  • использование функций ботов, не перечисленных выше (например, создание клавиатур я их сначала добавил, но в итоге они мне просто не пригодились);
  • создание списка заданий;
  • работу с Word-документом;
  • обеспечивающие функции логирование, тесты и т.п.;
  • общение с BotFather (создание бота, получение его токена и формирование списка команд подробно и понятно описано во многих источниках, вот первый попавшийся мануал).

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

Бизнес-функции бота


Очень кратко, чтобы проще было воспринимать код. Бот позволяет:

  • выдавать пользователю справочную текстовую информацию в ответ на команды /start, /help и /settings;
  • обрабатывать и запоминать пользовательские настройки, направленные текстовым сообщением заданного формата. Настроек три минимальное + максимальное число, используемые в заданиях, и количество страниц выгружаемого файла;
  • оповещать пользователя о несоблюдении им формата сообщения;
  • формировать Word-файл с заданиями на сложение, вычитание или вперемешку в ответ на команды /plus, /minus и /plusminus с использованием дефолтных или установленных пользователем настроек.

Можно потыкать MentalCalculationBot (должен работать). Выглядит так:



Общий порядок действий


  • разобраться с зависимостями;
  • создать класс бота и реализовать обработку текстовых сообщений пользователя, не являющихся командами;
  • создать классы команд;
  • прописать запуск приложения;
  • задеплоить на heroku.

Ниже подробно расписан каждый пункт.

Зависимости


Для управления зависимостями использовался Apache Maven. Нужные зависимости собственно Telegram Bots и Lombok, использовавшийся для упрощения кода (заменяет стандартные java-методы аннотациями).

Вот что вышло в
pom.xml
    <groupId>***</groupId>    <artifactId>***</artifactId>    <version>1.0-SNAPSHOT</version>    <name>***</name>    <description>***</description>    <packaging>jar</packaging>    <properties>        <java.version>11</java.version>        <maven.compiler.source>${java.version}</maven.compiler.source>        <maven.compiler.target>${java.version}</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>        <org.projectlombok.version>1.18.16</org.projectlombok.version>        <apache.poi.version>4.1.2</apache.poi.version>        <telegram.version>5.0.1</telegram.version>    </properties>    <dependencies>        <!-- Telegram API -->        <dependency>            <groupId>org.telegram</groupId>            <artifactId>telegrambots</artifactId>            <version>${telegram.version}</version>        </dependency>        <dependency>            <groupId>org.telegram</groupId>            <artifactId>telegrambotsextensions</artifactId>            <version>${telegram.version}</version>        </dependency>        <!-- Lombok -->        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>${org.projectlombok.version}</version>            <scope>compile</scope>        </dependency>    </dependencies>    <build>        <finalName>${project.artifactId}</finalName>        <plugins>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-compiler-plugin</artifactId>                <version>3.8.1</version>                <configuration>                    <release>${java.version}</release>                    <annotationProcessorPaths>                        <path>                            <groupId>org.projectlombok</groupId>                            <artifactId>lombok</artifactId>                            <version>${org.projectlombok.version}</version>                        </path>                    </annotationProcessorPaths>                </configuration>            </plugin>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-dependency-plugin</artifactId>                <version>3.1.2</version>                <executions>                    <execution>                        <id>copy-dependencies</id>                        <phase>package</phase>                        <goals>                            <goal>copy-dependencies</goal>                        </goals>                    </execution>                </executions>            </plugin>            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-surefire-plugin</artifactId>                <version>3.0.0-M5</version>            </plugin>        </plugins>    </build>


Класс бота и обработка текстовых сообщений


Мой класс Bot унаследован от TelegramLongPollingCommandBot, который, в свою очередь, наследуется от более распространённого в примерах TelegramLongPollingBot.

CommandBot хорош тем, что в нём уже реализованы приём и обработка команд то есть сообщений, начинающихся с "/". Можно создавать отдельные классы команд (подробнее о них ниже), инициализировать их в конструкторе бота и уже в них писать логику их обработки.

В классе Bot таким образом остаётся только логика обработки текстовых сообщений, не являющихся командами. В моём случае это пользовательские настройки или мусорные сообщения, не соответствующие формату. Для лаконичности логику их обработки тоже стоит вынести в отдельный вспомогательный класс, вызывая его метод из переопределенного метода processNonCommandUpdate(Update update) класса Bot.

В качестве параметров для инициализации бота используются его имя и токен, полученные от BotFather. В ходе разработки удобно создать тестового бота и прописать его параметры прямо в коде, чтобы проще было запускать бекенд локально и отлаживать прямо в Telegram, однако перед релизом стоит пересоздать бота и вынести эти настройки из кода например, в переменные окружения (об этом ниже).

Получился вот такой
Bot.java
import lombok.Getter;import org.telegram.telegrambots.extensions.bots.commandbot.TelegramLongPollingCommandBot;import org.telegram.telegrambots.meta.api.methods.send.SendMessage;import org.telegram.telegrambots.meta.api.objects.Message;import org.telegram.telegrambots.meta.api.objects.Update;import org.telegram.telegrambots.meta.api.objects.User;import org.telegram.telegrambots.meta.exceptions.TelegramApiException;import ru.taksebe.telegram.mentalCalculation.telegram.commands.operations.MinusCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.operations.PlusCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.operations.PlusMinusCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.service.HelpCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.service.SettingsCommand;import ru.taksebe.telegram.mentalCalculation.telegram.commands.service.StartCommand;import ru.taksebe.telegram.mentalCalculation.telegram.nonCommand.NonCommand;import ru.taksebe.telegram.mentalCalculation.telegram.nonCommand.Settings;import java.util.HashMap;import java.util.Map;public final class Bot extends TelegramLongPollingCommandBot {    private final String BOT_NAME;    private final String BOT_TOKEN;    //Класс для обработки сообщений, не являющихся командой    private final NonCommand nonCommand;    /**     * Настройки файла для разных пользователей. Ключ - уникальный id чата     */    @Getter    private static Map<Long, Settings> userSettings;    public Bot(String botName, String botToken) {        super();        this.BOT_NAME = botName;        this.BOT_TOKEN = botToken;        //создаём вспомогательный класс для работы с сообщениями, не являющимися командами        this.nonCommand = new NonCommand();        //регистрируем команды        register(new StartCommand("start", "Старт"));        register(new PlusCommand("plus", "Сложение"));        register(new MinusCommand("minus", "Вычитание"));        register(new PlusMinusCommand("plusminus", "Сложение и вычитание"));        register(new HelpCommand("help","Помощь"));        register(new SettingsCommand("settings", "Мои настройки"));        userSettings = new HashMap<>();    }    @Override    public String getBotToken() {        return BOT_TOKEN;    }    @Override    public String getBotUsername() {        return BOT_NAME;    }    /**     * Ответ на запрос, не являющийся командой     */    @Override    public void processNonCommandUpdate(Update update) {        Message msg = update.getMessage();        Long chatId = msg.getChatId();        String userName = getUserName(msg);        String answer = nonCommand.nonCommandExecute(chatId, userName, msg.getText());        setAnswer(chatId, userName, answer);    }    /**     * Формирование имени пользователя     * @param msg сообщение     */    private String getUserName(Message msg) {        User user = msg.getFrom();        String userName = user.getUserName();        return (userName != null) ? userName : String.format("%s %s", user.getLastName(), user.getFirstName());    }    /**     * Отправка ответа     * @param chatId id чата     * @param userName имя пользователя     * @param text текст ответа     */    private void setAnswer(Long chatId, String userName, String text) {        SendMessage answer = new SendMessage();        answer.setText(text);        answer.setChatId(chatId.toString());        try {            execute(answer);        } catch (TelegramApiException e) {            //логируем сбой Telegram Bot API, используя userName        }    }}


Класс обработки текстовых сообщений
NonCommand.java
import ru.taksebe.telegram.mentalCalculation.exceptions.IllegalSettingsException;import ru.taksebe.telegram.mentalCalculation.telegram.Bot;/** * Обработка сообщения, не являющегося командой (т.е. обычного текста не начинающегося с "/") */public class NonCommand {    public String nonCommandExecute(Long chatId, String userName, String text) {        Settings settings;        String answer;        try {            //создаём настройки из сообщения пользователя            settings = createSettings(text);            //добавляем настройки в мапу, чтобы потом их использовать для этого пользователя при генерации файла            saveUserSettings(chatId, settings);            answer = "Настройки обновлены. Вы всегда можете их посмотреть с помощью /settings";            //логируем событие, используя userName        } catch (IllegalSettingsException e) {            answer = e.getMessage() +                    "\n\n Настройки не были изменены. Вы всегда можете их посмотреть с помощью /settings";            //логируем событие, используя userName        } catch (Exception e) {            answer = "Простите, я не понимаю Вас. Возможно, Вам поможет /help";            //логируем событие, используя userName        }        return answer;    }    /**     * Создание настроек из полученного пользователем сообщения     * @param text текст сообщения     * @throws IllegalArgumentException пробрасывается, если сообщение пользователя не соответствует формату     */    private Settings createSettings(String text) throws IllegalArgumentException {        //отсекаем файлы, стикеры, гифки и прочий мусор        if (text == null) {            throw new IllegalArgumentException("Сообщение не является текстом");        }        //создаём из сообщения пользователя 3 числа-настройки (min, max, listCount) либо пробрасываем исключение о несоответствии сообщения требуемому формату        return new Settings(min, max, listCount);    }    /**     * Добавление настроек пользователя в мапу, чтобы потом их использовать для этого пользователя при генерации файла     * Если настройки совпадают с дефолтными, они не сохраняются, чтобы впустую не раздувать мапу     * @param chatId id чата     * @param settings настройки     */    private void saveUserSettings(Long chatId, Settings settings) {        if (!settings.equals(Settings.getDefaultSettings())) {            Bot.getUserSettings().put(chatId, settings);        }    }}


Классы команд


Все классы команд наследуются от BotCommand.

Команды в моём боте делятся на 2 группы:

  • Сервисные возвращают справочную информацию;
  • Основные формируют файл с заданиями.

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

Начнём с более простых Сервисных команд. В абстрактный суперкласс вынесен метод отправки пользователю ответа, а в классах команд формируется текст ответа.

Абстрактный суперкласс Сервисных команд
ServiceCommand.java
import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand;import org.telegram.telegrambots.meta.api.methods.send.SendMessage;import org.telegram.telegrambots.meta.bots.AbsSender;import org.telegram.telegrambots.meta.exceptions.TelegramApiException;/** * Суперкласс для сервисных команд */abstract class ServiceCommand extends BotCommand {    ServiceCommand(String identifier, String description) {        super(identifier, description);    }    /**     * Отправка ответа пользователю     */    void sendAnswer(AbsSender absSender, Long chatId, String commandName, String userName, String text) {        SendMessage message = new SendMessage();        //включаем поддержку режима разметки, чтобы управлять отображением текста и добавлять эмодзи        message.enableMarkdown(true);        message.setChatId(chatId.toString());        message.setText(text);        try {            absSender.execute(message);        } catch (TelegramApiException e) {            //логируем сбой Telegram Bot API, используя commandName и userName        }    }}


Класс Сервисной команды на примере
StartCommand.java
import org.telegram.telegrambots.meta.api.objects.Chat;import org.telegram.telegrambots.meta.api.objects.User;import org.telegram.telegrambots.meta.bots.AbsSender;/** * Команда "Старт" */public class StartCommand extends ServiceCommand {    public StartCommand(String identifier, String description) {        super(identifier, description);    }    @Override    public void execute(AbsSender absSender, User user, Chat chat, String[] strings) {        //формируем имя пользователя - поскольку userName может быть не заполнено, для этого случая используем имя и фамилию пользователя        String userName = (user.getUserName() != null) ? user.getUserName() :                String.format("%s %s", user.getLastName(), user.getFirstName());        //обращаемся к методу суперкласса для отправки пользователю ответа        sendAnswer(absSender, chat.getId(), this.getCommandIdentifier(), userName,                "Давайте начнём! Если Вам нужна помощь, нажмите /help");    }}


В суперклассе Основных команд, помимо аналогичного метода отправки ответов, содержится формирование Word-документа.
OperationCommand.java
import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand;import org.telegram.telegrambots.meta.api.methods.send.SendDocument;import org.telegram.telegrambots.meta.api.methods.send.SendMessage;import org.telegram.telegrambots.meta.api.objects.InputFile;import org.telegram.telegrambots.meta.bots.AbsSender;import org.telegram.telegrambots.meta.exceptions.TelegramApiException;import ru.taksebe.telegram.mentalCalculation.calculation.Calculator;import ru.taksebe.telegram.mentalCalculation.calculation.PlusMinusService;import ru.taksebe.telegram.mentalCalculation.enums.OperationEnum;import ru.taksebe.telegram.mentalCalculation.fileProcessor.WordFileProcessorImpl;import ru.taksebe.telegram.mentalCalculation.telegram.Settings;import java.io.FileInputStream;import java.io.IOException;import java.util.List;/** * Суперкласс для команд создания заданий с различными операциями */abstract class OperationCommand extends BotCommand {    private PlusMinusService service;    OperationCommand(String identifier, String description) {        super(identifier, description);        this.service = new PlusMinusService(new WordFileProcessorImpl(), new Calculator());    }    /**     * Отправка ответа пользователю     */    void sendAnswer(AbsSender absSender, Long chatId, List<OperationEnum> operations, String description, String commandName, String userName) {        try {            absSender.execute(createDocument(chatId, operations, description));        } catch (IOException | IllegalArgumentException e) {            sendError(absSender, chatId, commandName, userName);            e.printStackTrace();        } catch (TelegramApiException e) {            //логируем сбой Telegram Bot API, используя commandName и userName        }    }    /**     * Создание документа для отправки пользователю     * @param chatId id чата     * @param operations список типов операций (сложение и/или вычитание)     * @param fileName имя, которое нужно присвоить файлу     */    private SendDocument createDocument(Long chatId, List<OperationEnum> operations, String fileName) throws IOException {        FileInputStream stream = service.getPlusMinusFile(operations, Bot.getUserSettings(chatId));        SendDocument document = new SendDocument();        document.setChatId(chatId.toString());        document.setDocument(new InputFile(stream, String.format("%s.docx", fileName)));        return document;    }    /**     * Отправка пользователю сообщения об ошибке     */    private void sendError(AbsSender absSender, Long chatId, String commandName, String userName) {        try {            absSender.execute(new SendMessage(chatId.toString(), "Похоже, я сломался. Попробуйте позже"));        } catch (TelegramApiException e) {            //логируем сбой Telegram Bot API, используя commandName и userName        }    }}


Класс Основной команды на примере
PlusMinusCommand.java
import org.telegram.telegrambots.meta.api.objects.Chat;import org.telegram.telegrambots.meta.api.objects.User;import org.telegram.telegrambots.meta.bots.AbsSender;import ru.taksebe.telegram.mentalCalculation.enums.OperationEnum;/** * Команда получение файла с заданиями на сложение и вычитание */public class PlusMinusCommand extends OperationCommand {    public PlusMinusCommand(String identifier, String description) {        super(identifier, description);    }    @Override    public void execute(AbsSender absSender, User user, Chat chat, String[] strings) {        //формируем имя пользователя - поскольку userName может быть не заполнено, для этого случая используем имя и фамилию пользователя        String userName = (user.getUserName() != null) ? user.getUserName() :                String.format("%s %s", user.getLastName(), user.getFirstName());        //обращаемся к методу суперкласса для формирования файла на сложение и вычитание (за это отвечает метод getPlusMinus() перечисления OperationEnum) и отправки его пользователю        sendAnswer(absSender, chat.getId(), OperationEnum.getPlusMinus(), this.getDescription(), this.getCommandIdentifier(), userName);    }}


Приложение


В методе main инициализируется TelegramBotsApi, в котором и регистрируется Bot.

TelegramBotsApi в качестве параметра принимает Class<? extends BotSession>. Если нет никаких заморочек с прокси, можно использовать DefaultBotSession.class.

Чтобы получать имя и токен бота как переменные окружения, необходимо использовать System.getenv().

Получаем вот такой
MentalCalculationApplication.java
import org.telegram.telegrambots.meta.TelegramBotsApi;import org.telegram.telegrambots.meta.exceptions.TelegramApiException;import org.telegram.telegrambots.updatesreceivers.DefaultBotSession;import ru.taksebe.telegram.mentalCalculation.telegram.Bot;import java.util.Map;public class MentalCalculationApplication {    private static final Map<String, String> getenv = System.getenv();    public static void main(String[] args) {        try {            TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);            botsApi.registerBot(new Bot(getenv.get("BOT_NAME"), getenv.get("BOT_TOKEN")));        } catch (TelegramApiException e) {            e.printStackTrace();        }    }}


Деплой на heroku


Для начала нужно создать в корне проекта файл Procfile и написать в него одну строку:
worker: java -Xmx300m -Xss512k -XX:CICompilerCount=2 -Dfile.encoding=UTF-8 -cp ./target/classes:./target/dependency/* <путь до приложения, в моём случае ru.taksebe.telegram.mentalCalculation.MentalCalculationApplication>
, где worker это тип процесса.

Если в проекте используется версия Java, отличная от 8, также необходимо создать в корне проекта файл system.properties и прописать в нём одну строку:
java.runtime.version=<версия Java>

Далее порядок такой:

  1. Регистрируемся на heroku и идём в консоль;
  2. mvn clean install;
  3. heroku login после выполнения потребуется нажать любую клавишу и залогиниться в открывшемся окне браузера;
  4. heroku create <имя приложения> создаём приложение на heroku;
  5. git push heroku master пушим в репозиторий heroku;
  6. heroku config:set BOT_NAME=<имя бота> добавляем имя бота в переменные окружения;
  7. heroku config:set BOT_TOKEN=<токен бота> добавляем токен бота в переменные окружения;
  8. heroku config:get BOT_NAME (аналогично BOT_TOKEN) убеждаемся, что переменные окружения установлены верно;
  9. heroku ps:scale worker=1 устанавливаем количество контейнеров (dynos) для типа процесса worker (ранее мы выбрали этот тип в Procfile), при этом происходит рестарт приложения;
  10. В интерфейсе управления приложением в личном кабинете на heroku переходим к логам (прячутся под кнопкой More в правом верхнем углу) и убеждаемся, что приложение запущено;
  11. Тестируем бота через Telegram.

Если вы храните код на GitHub, то в интерфейсе управления приложением в личном кабинете на heroku на вкладке Deploy вы можете в дальнейшем переключить деплой на GitHub-репозиторий (по запросу или автоматически), чтобы не пушить параллельно в два репозитория.

Вместо заключения


Как выяснилось, не только лишь все видели чудесный советский мультик про козлёнка, который учился считать до 10.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru