Как стать автором
Обновить

Написание базового wave-робота на python'e

Время на прочтение6 мин
Количество просмотров4.5K
Слава Роботам
По лету мне достался инвайт в гугльвейв песочницу. Но в этой самой песочнице было очень много народу, все волны были публичными, и мой бедный нетбук только с большим скрипом переваривал всю эту активность, так что, немного поигравшись, на сендбокс я забил :)

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


Результатом разбирательств стал такой вот базовый робот: bakarobo@appsot.com, который умеет пока всего-ничего:

по команде !br:bor! достать случайную цитату с баша
по команде !br:rb! достать фото дня с rosbest
по команде !br:BakaRobo! откликнуться :)
и ругаться в ответ на все незнакомые команды.


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

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

Начнем с того, что роботов размещать следует на Google App Engine. Как создать там приложение и скачать инструментарий для коммитов кода я рассказывать не буду — там все очень понятно объяснено.

Итак, мы скачали инструменты, и в некоей папке на диске у нас возникла примерно такая картинка:

.
..
google_appengine
our_robot


Где our_robot — папка, в которой будет наш робот. И вот в эту-то папку мы и скачиваем и распаковываем вот этот архивчик с code.google.com — это, собственно, питоновская апишка.

Теперь мы готовы к собственно разработке.

На всякий случай: коммит кода в аппенджин делается так:
python ./google_appengine/appcfg.py update ./our_robot/ — потом нас спрашивают о мыле и пароле и дают залить файло.


В базовом случае главных файлов в проекте будет три:

our_robot.py - собственно, код робота
app.yaml - нечто вроде манифеста
_wave/capabilities.xml - файлик, объявляющий эвенты, которые хочет слушать робот.

Дополнение от farcaller:
Питоновый апи xml-ку кастати сам генерирует, на базе аргументов к robot.Robot, а вот жавовому надо писать ручками.
Так что, видимо, от некоторого количества телодвижений в процессе разработки можно отказаться.

Список эвентов можно посмотреть тут, но самые важные для робота, на мой взгляд, это:
WAVELET_SELF_ADDED — срабатывает, когда робота добавляют в волну, в этот момент неплохо показать маленькое инфо по использованию;
BLIP_SUBMITTED — срабатывает, когда создается/редактируется блип волны, причем не в момент написания текста, а когда уже жмякнута кнопка «Done».


Поехали дальше.
Манифест app.yaml выглядит, судя по туториалу на code.google.com, примерно так:

application: our_robot
version: 1
runtime: python
api_version: 1
handlers:
- url: /_wave/.*
  script: our_robot.py
- url: /assets
  static_dir: assets
- url: /icon.png
  static_files: icon.png
  upload: icon.png


Тут, вроде, все понятно. Название робота, версии, чем запускаем, версия апи и хендлеры для разных урлов.

Единственное, на что следует обратить внимание — это "-url: /icon.png" в разделе хендлеров. Этого, кажется, нету в туториале, конструкция позволяет задать способ обращения с иконкой робота. Рисуем ее в пнгшку, сохраняем в папку робота, объявляем внутри питоновского файла :)

capabilities.xml, опять же, по тутору, выглядит тоже незамысловато:

<?xml version="1.0" encoding="utf-8"?>
<w:robot xmlns:w="http://wave.google.com/extensions/robots/1.0">
 <w:capabilities>
    <w:capability name="WAVELET_SELF_ADDED" content="true" />
    <w:capability name="BLIP_SUBMITTED" content="true" />
 </w:capabilities>
 <w:version>1</w:version>
 </w:robot>


* This source code was highlighted with Source Code Highlighter.


Собственно, в этом файле и менять-то особо нечего: только номер версии да эвенты, которые мы хотим слушать.

А вот после того, как вся эта предварительная суматоха закончилась и начинается, собственно, довольно приятная возня с написанием питоновского кода робота.

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

Итак, в общем и целом код болванки для робота выглядит примерно так:

from waveapi import events
from waveapi import model
from waveapi import robot
def OnRobotAdded(properties, context):
    pass
def OnBlipSubmitted(properties, context):
    pass
if __name__ == '__main__':
 myRobot = robot.Robot('our_robot',
     image_url='http://our_robot.appspot.com/icon.png', #иконка контакта для робота
     version='2.3', #версия
     profile_url='http://our_robot.appspot.com/') #адрес профиля контакта
 # Назначаем события:
 myRobot.RegisterHandler(events.WAVELET_SELF_ADDED, OnRobotAdded)
 myRobot.RegisterHandler(events.BLIP_SUBMITTED, OnBlipSubmitted)
 # Запуск
 myRobot.Run()


* This source code was highlighted with Source Code Highlighter.


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

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

Во-первых, чтобы в функциях обработки событий достать блип, с которым событие произошло (если, конечно, это событие произошло с блипом), используем
blip = context.GetBlipById(properties['blipId'])

Во-вторых, чтобы получить текст блипа и с ним оперировать, делаем
doc = blip.GetDocument()
contents = doc.GetText()


Соответственно, чтобы заменить некоторый кусок текста на другой, используем на полученный doc
doc.SetTextInRange(model.document.Range(НАЧАЛО, КОНЕЦ), НОВЫЙ_ТЕКСТ)

Чтобы вставить кусок текста в любое место:
doc.InsertText(НАЧАЛО, ТЕКСТ)

Чтобы добавить кусок текста в конец:
doc.AppendText(ТЕКСТ)

Чтобы вставить картинку:
В конец — doc.AppendElement(model.document.Image(АДРЕС_КАРТИНКИ, ШИРИНА, ВЫСОТА, ATTACHMENT_ID, АЛЬТ))
В определенное место — doc.InsertElement(НАЧАЛО, model.document.Image(АДРЕС_КАРТИНКИ, ШИРИНА, ВЫСОТА, ATTACHMENT_ID, АЛЬТ))

В общем, полезно посмотреть вот этот референс для того, чтобы узнать, что можно делать с документом. Для того, чтобы узнать виды элементов, которые можно создавать, смотрим референсы по waveapi.document.* — там есть и Image, и Link и даже Gadget.

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

doc.SetAnnotation(model.document.Range(НАЧАЛО, КОНЕЦ), ТИП, ЗНАЧЕНИЕ)

Причем ТИП — это штука, которая описывает, что за аннотацию мы добавляем. Самый важный, имхо, это 'style/STYLE_PROP', где STYLE_PROP — это запись css атрибута в js виде.
Вдруг кто не знает — это трансформированная запись свойств css, используемая в js-скриптах, показать ее суть проще на примерах :) Например, color — это просто color, а вот font-size — это fontSize. В смысле, там, где в css дефис, в этой записи дефиса нет, но каждое слово кроме первого начинается с большой буквы. backgroundColor, backgroundImage, marginTop, и так далее.

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

doc.DeleteAnnotationsByName(ТИП)

А можно почистить от аннотаций определенного типа только некоторый диапозон текста:

doc.DeleteAnnotationsInRange(model.document.Range(НАЧАЛО, КОНЕЦ), ТИП)

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

Чтобы аннотировать блип целиком, используем:
doc.AnnotateDocument(ТИП, ЗНАЧЕНИЕ)

Чтобы узнать, есть ли в блипе какой-то тип аннотации, вызывается
doc.HasAnnotation(ТИП)

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

P.S.: Кстати, замеченная мной бага, которая сильно помешала разработке поначалу: в вкладке логов в appspot'е (единственном доступном нам инструменте дебага) уровень показываемых сообщений по дефолту выставлен в Error. Хохма в том, что если в записи лога сначала идет кусок уровня, например, Info, и только потом кусок с ошибкой — такую запись нам не покажут. Так что — переставляем уровень оповещений на Debug и радуемся возможности рассмотреть все случившиеся ошибки.

P.P.S.: Спасибо, перенес в соответствующий блог.
Теги:
Хабы:
Всего голосов 74: ↑62 и ↓12+50
Комментарии22

Публикации

Истории

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань