python

Учим Raspberry Pi принимать Telegram'мы с помощью Bot API и Python

  • среда, 1 июля 2015 г. в 02:11:48
http://habrahabr.ru/post/261473/

Давно хотел прикрутить к своей домашней Raspberry Pi удобный интерфейс «общения», который бы удовлетворял главному требованию — простота и лёгкость, с доступом из любой точки мира и с помощью любого оборудования (но в первую очередь — со смартфона).

В связи с отсутствием дома выделенного IP и наличием сурового и неподкупного NAT варианты с SSH клиентами и web-интерфейсами отпали сразу. Для небольших потребностей решение тоже должно быть простое, быстрое и, в качестве бонуса, надежное. Так что идея использования протокола одного из распространенных мессенджеров показалась мне весьма привлекательной. Под прицел попали Jabber, Telegram и WhatsApp.

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

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

Результат поиска в рунете по словосочетанию «Telegram & Raspberry» оказался богат только на статью с Хабра «Raspberry и Telegram: предпосылки создания умного дома», в которой описываются базовые манипуляции с клиентом Telegram. Кстати, достаточно сырой продукт и заставить его нормально работать мне так и не удалось (на ровном месте через раз отказывается парсить одни и те же команды). Но, к счастью, мне он уже не нужен.

Итак, нам необходимо создать бота, для чего в любом клиенте Telegram'a (желательно последней версии) находим контакт с именем BotFather и просим его о /help. На что он ответит в достаточной мере подробной инструкцией и останется только следовать ей. Команды для совсем лентяев:

/newbot
<отображаемое имя нового бота>
<username нового бота>

Готово! Теперь BotFather предложит нам запомнить\сохранить token для досупа к боту через HTTP API, который нам скоро пригодится.

Так как программист я очень начинающий, то хорошо знаком только с Python, который, тем не менее, прекрасно подходит для данной задачи. Начнем.

Для существенного облегчения жизни и сокращения кода, предлагаю установить библиотеку для упрощения HTTP-запросов requests с помощью команды:

pip install requests

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

telegram.py (python2.7)
# -*- coding: utf-8 -*-
import requests
import time

requests.packages.urllib3.disable_warnings() # Подавление InsecureRequestWarning, с которым я пока ещё не разобрался

# Ключ авторизации Вашего бота Вы можете получить в любом клиенте Telegram у бота @BotFather
# ADMIN_ID - идентификатор пользователя (то есть Вас), которому подчиняется бот
# Чтобы определить Ваш ID, я предлагаю отправить боту сообщение от своего имени (аккаунта) через любой клиент
# А затем получить это сообщения с помощью обычного GET запроса
# Для этого вставьте в адресную строку Вашего браузера следующий адрес, заменив <token> на свой ключ:
# https://api.telegram.org/bot<token>/getUpdates
# Затем, в ответе найдите объект "from":{"id":01234567,"first_name":"Name","username":"username"}
# Внимательно проверьте имя, логин и текст сообщения
# Если всё совпадает, то цифровое значение ключа "id" - это и есть ваш идентификатор

# Переменным ADMIN_ID и TOKEN необходимо присвоить Вашим собственные значения
INTERVAL = 5 # Интервал проверки наличия новых сообщений (обновлений) на сервере в секундах
ADMIN_ID = 01234567 # ID пользователя. Комманды от других пользователей выполняться не будут
URL = 'https://api.telegram.org/bot' # Адрес HTTP Bot API
TOKEN = '012345678:???????????????????????' # Ключ авторизации для Вашего бота
offset = 0 # ID последнего полученного обновления

def make_url_query_string(params):
	"""
	Конвертирование словаря параметров в строку типа "URL query string"
	Пример: '(http://site.com/home)?param1=value1&param2=value2'
	"""
	return '?' + '&'.join([str(key) + '=' + str(params[key]) for key in params])

def check_updates(limit=5):
	"""
	Проверка обновлений на сервере и инициация действий, в зависимости от команды
	ToDo: 	1) повторная отправка при неудаче
			2) сопоставление команд и действий
			3) добавить логгирование

	"""
	global offset
	params = make_url_query_string({'offset': offset+1, 'limit': limit, 'timeout': 0})
	request = requests.get(URL + TOKEN + '/getUpdates' + params) # Отправка запроса обновлений
	if not request.status_code == 200: return False # Проверка ответа сервера
	if not request.json()['ok']: return False # Проверка успешности обращения к API
	if not request.json()['result']: return False # Проверка наличия обновлений в возвращенном списке
	for update in request.json()['result']: # Проверка каждого элемента списка
		offset = update['update_id'] # Извлечение ID сообщения
		from_id = update['message']['from']['id'] # Извлечение ID отправителя
		if from_id  <> ADMIN_ID: # Проверка ID отправителя и если контакт не является администратором, то
			send_respond("You're not autorized to use me!", from_id) # ему отправляется соответствующее уведомление
			continue # и цикл переходит к следующему сообщению
		message = update['message']['text'] # Извлечение текста сообщения

		# Следующий код выводит в консоль ID и текст сообщения
		print '>> OFFSET: ', offset
		print '>> MESSAGE:', message
		print '-' * 10
		send_respond('Принято!', ADMIN_ID)
		###

		# Место для кода, выполняющего определенные команды,
 		# в зависимости от содержания полученного сообщения
 		if message == 'ping':
                    send_respond('pong', from_id)

def send_respond(text, chat_id):
	"""Отправка текстового сообщения по chat_id или user_id (чем они отличаются?)
	ToDo: повторная отправка при неудаче"""
	params = make_url_query_string({'chat_id': chat_id, 'text': text}) # Преобразование параметров
	request = requests.get(URL + TOKEN + '/sendMessage' + params) # HTTP запрос
	if not request.status_code == 200: return False # Проверка ответа сервера
	if not request.json()['ok']: return False # Проверка успешности обращения к API
	return True

if not __name__ == "__main__": exit()

while True:
	try:
		check_updates()
		time.sleep(INTERVAL)
	except KeyboardInterrupt:
		print 'Прервано пользователем..'
		break


Советы
Чтобы запустить данный скрипт в фоновом режиме на Raspberry Pi, можно воспользоваться двумя способами:
1) С помощью screen. Инструкция по использованию тут.
2) Командами:

python telegram.py
CTRL+Z
bg

Если хотите поставить этот скрипт в автозапуск, необходимо в файл /etc/rc.local, перед строкой 'exit 0', добавить:
python <путь к файлу>/telegram.py

Например так:
nano /etc/rc.local
    ...
    python /home/pi/telegram.py

    exit 0


И естественно, на вашей Raspberry должен быть установлен python2.7.

Это, конечно же, только начало, наброски. Чуть позже прикручу несколько интересных функций. Например, получение снимка с камеры по команде и некоторые посложнее, такие как как управление гирляндой на WS2801 и другие.

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

Также, как вы уже заметили, скрипт проверяет сообщения с определенным промежутком времени. Реализовать прием WebHook без посредника не представляется возможным. Игрался со значениями «timeout» в методе «getUpdates», — безрезультатно. Буду благодарен за любые идеи и на этот счет.

[ Telegram Bot API ]