Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #133, ЯНВАРЬ 2010 г.

Робот для Google Wave. Напишем его на Python’е!

Роман «presidentua» Хоменко, http://tutamc.com


«Ладно, я построю свой собственный модуль с блек-джеком и шлюхами. Вообще-то, к черту модуль и блек-джек»... Какие хорошие советы могут давать роботы! Отлично, сегодня я расскажу тебе, как построить своего маленького робота. Например, для новомодного Google Wave.

Google Wave получил хороший пиар, и думаю, в этом мире не сыскать того, кто не слышал о нем (не считая соседа Толика, который в запое :)). Нельзя исключить, что пиар даже немножко повредил Волне, позиционируя это явление природы как убийцу почты, форумов, и чуть ли не всего остального интернета. Лично для меня Волна - это достаточно классно сделанный IRC с хорошим API. С помощью которого мы можем создавать гаджеты и, что интереснее, роботов, которые могут расширять возможности Wave под любые нужды.

Робот API

Робот для Wave будет представлять собой e-mail с аватаркой, описанием и закрепленными за ним событиями. Событий у нас будет около 15 штук, но для большинства случаев хватит и двух:

WAVELET_SELF_ADDED
BLIP_SUBMITTED

Первое событие возникает, когда мы добавляем робота на какую-либо волну. Второе событие проявляется в случае, когда кто-то добавляет сообщение, причем это сообщение возникает в момент нажатия на кнопку «Done». Вообще, в контексте программирования волны Google вводит несколько понятий:

  • wave - полностью весь Wave;
  • wavelet - обозначает конкретную волну;
  • blip - одно сообщение.

Помнишь в позапрошлом номере я тебе рассказывал о Google App Engine(GAE)? Так вот, на данный момент роботов можно строить лишь с использованием GAE.

К практике!

Теория в случаях с Гуглом сложнее, чем практика, поэтому не будем на ней задерживаться и перейдем сразу к созданию робота. Все проекты на GAE начинаются с пустой папки и файла app.yaml, в котором мы установим, что все запросы от wave будет обрабатывать скрипт jbot.py:

handlers:
- url: /_wave/.*
script: jbot.py
- url: /assets
static_dir: assets

Давай теперь определим ТТХ нашего робота. Пусть он должен уведомлять нас по джабберу о новых сообщениях в Волну. И, соответственно, мы должны иметь возможность подписывать свой джаббер на обновления и удаляться из рассылки. В тексте приходящего сообщения будет содержаться мейл автора и текст новой мессаги. Нам нужно будет перехватывать событие WAVELET_SELF_ADDED для вывода справки о командах и перехватывать BLIP_SUBMITTED с целью рассылки уведомлений.
Начнем прогить файл робота jbot.py, поместив в начале импорт необходимых библиотек:

from waveapi import events
from waveapi import robot

Создадим объект робота, передав название, адрес аватарки, версию и адрес сайта робота. Заметь, что версию нужно обязательно менять, если изменяется список событий, потому что новые события в этом случае не будут работать.

myRobot = robot.Robot('w-mailrobot',
image_url='http://w-mailrobot.appspot.com/images/avatar.png',
version='1',
profile_url='http://w-mailrobot.appspot.com/')

Теперь добавим перехват нужных событий и сам запуск робота:

myRobot.RegisterHandler(events.WAVELET_SELF_ADDED, OnRobotAdded)
myRobot.RegisterHandler(events.BLIP_SUBMITTED, OnBlipSubmitted)
myRobot.Run()

В процессе регистрации перехватчиков мы указывали OnRobotAdded, OnBlipSubmitted - это названия функций, которые будут вызываться при активизации события. Они должны принимать два параметра: properties, context.

В properties будет содержаться информация относительно конкретного события, а в context - информация об окружении, о волне, где событие возникло. Именно через работу с context мы можем добавить новое сообщение в волну, вызвав такую длинную цепочку функций:

context.GetRootWavelet().CreateBlip().GetDocument().SetText(string)

Также из нее можно достать идентификатор волны, который нам позже понадобится:

waveid = context.GetRootWavelet().GetWaveId()

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

def addBlip(context, string):
context.GetRootWavelet().CreateBlip().GetDocument().SetText(string)

def OnRobotAdded(properties, context):
addBlip(context,"I'm alive!\n
Command:\n
wabber-bot add me: jabber@jabber.ja\n
wabber-bot remove me: jabber@jabber.ja")

Здесь для будущего удобства мы добавили функцию по добавлению сообщений - addBlip. А потом уже отправили само сообщение.

Джаббер

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

inbound_services:
- xmpp_message

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

from google.appengine.api.xmpp import send_message, send_invite
#просьба авторизации
send_invite("кому")
#отослать сообщение
status = send_message("кому", "текст сообщения")

В процессе программирования под Google Wave иногда не очень понятно, почему что-то не работает. Исходя из этого, нам обязательно нужно использовать модуль logging. В GAE после его импортирования мы можем добавлять сообщения разной степени важности:

logging.info('info')
logging.error('error')

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

Был Blip?

Для завершения работы осталось сделать последний шаг - написать OnBlipSubmitted - обработчик события о новом сообщении. Функция эта будет способна обрабатывать три ситуации:

  • была команда «добавить джабер в лист оповещения»;
  • была команда «удалить джабер с листа оповещения»;
  • не было команды, тогда - разослать это сообщения по подписчикам.

Алгоритм ясен? Выразим его скупыми строчками программного кода:

def OnBlipSubmitted(properties, context):
blip = context.GetBlipById(properties['blipId'])
text_blip = blip.GetDocument().GetText()

if text_blip.startswith('wabber-bot add me:'):
#добавить юзера
return

if text_blip.startswith('wabber-bot remove me:'):
#удалить юзера
return

#разослать всем сообщение

База данных

Для сохранения привязки джаббер-акка к волне нам будет нужна база с двумя полями, одним для идентификатора волны и вторым для джаббера:

from google.appengine.ext import db
class WaveModel(db.Model):
wave = db.StringProperty()
user = db.StringProperty()

Все готово к непосредственной обработке команд и добавлению пользователей в БД:

if text_blip.startswith('wabber-bot add me:'):
creator = text_blip[18:].strip()
count = WaveModel.all().filter('wave = ', waveid).
filter('user =', creator).count()
if count:
return
WaveModel(
wave = waveid,
user = creator
).put()
send_invite(creator)
addBlip(context, "%s was added"%creator)
return

Как видим, мы не только добавляем юзера в базу данных, но и присылаем запрос авторизации – чтобы остальные сообщения доставлялись без затруднений.

Принцип удаления пользователя из рассылки практически аналогичен, а точнее – намного проще. Ознакомься с тремя главными строчками:

rez = WaveModel.all().filter('wave = ', waveid).filter('user =', creator)
for i in rez:
i.delete()

Если же никакой команды нет, то достаем из базы всех, кто подписался, и отправляем им сообщение:

all_u = WaveModel.all().filter('wave = ', waveid)
for u in all_u:
status = send_message(u.user, "New message from
%s:\n%s"%(blip_creator, text_blip))

Видишь, как просто? Мы так долго готовились к реализации работы с командами, а на деле получилось лишь несколько строчек сухого и безжалостного программного кода.

Люди или Бендер?

После написания робота я залил его на GAE, и теперь ты можешь добавить wabber-robot@appspot.com к себе в контакты. По идее, нужно еще добавить подтверждения для джабера, чтобы только пользователь мог себя добавить в список. А то ведь получается, что любой джаббер-аккаунт можно будет заспамить…хотя, наверное, это не баг, а фича. В общем, до новых встреч! Ну, Фрай, было приятно познакомиться, пойду, убью себя ©.

Еще парочка Wave api-команд

Допустим, мы получили сообщение:

blip = context.GetBlipById(properties['blipId'])

Добавить текст в какую-то позицию:

blip.GetDocument().InsertText(pos, text)

Добавить текст в конец сообщения:

blip.GetDocument().AppendText(ТЕКСТ)

Удалить можно командой:

blip.Delete()

Создать ссылку:

doc = wavelet.CreateBlip().GetDocument()
doc.SetAnnotation(Annotation(Range(0, 5), "link/manual", "http://xakep.ru"))
doc.SetText("XAKEP")

Парочка фактов

Работа над проектом Wave началась в 2007 году. Основными разработчиками программного обеспечения были братья Ларс и Йенс Расмуссены, также являющиеся главными разработчиками Google Maps.

Google Wave использует технологии, предоставленные возможностями HTML 5. Некоторые функции в настоящее время доступны только после установки Google Gears.

Технология Google Wave подразумевает открытость протоколов и программного обеспечения (под лицензией Apache Software License), что позволяет развертывать собственные серверы Google Wave как подключенные и синхронизируемые, так и не подключенные к серверам Google. Во втором случае сообщения между пользователями локальной инсталляции не будут передаваться во внешнюю сеть.
Название навеяно сериалом «Светлячок», в котором «волна» — это электронная коммуникация (часто с видео-звонком или видео-сообщением). На презентации для разработчиков Google I/O Ларс Расмуссен отвечал на некоторые фразы - «блестяще» (англ. shiny), то есть использовал слово, обычно применяемое в этом сериале в смысле «здорово». В качестве сообщения об отказе системы в Google Wave использована популярная цитата из сериала — «Будь проклято ваше внезапное, но неизбежное предательство!».

INFO

Хорошо быть первым? Да! Поэтому к моменту запуска Wave в паблик желательно уметь написать к нему крутой спамер или что-то в этом роде :).

DVD

Исходники бота со всеми либами ждут тебя на диске.

VIDEO

Как всегда, снял для тебя видео про тестирование своего бота. Надеюсь, заценишь!

WWW

Содержание