воскресенье, 24 августа 2008 г.

Ruby, jabber,etc...

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

В последнее время perl в около-IT'шной среде принятно ругать(это наверное тема для отдельного разговора), а хвалят в связи с этим чаще всего python, иногда ruby. C python'ом я когда-то игрался, и мне даже нравилось, но сейчас особых чувств к этому языку не испытываю. С ruby как-то особо не сталкивался, хотя судя по отзывам штука достаточно интересная. Во время небольшой передышки на работе нахватался кусков из книг, а вчера в очередной раз посетило желание поиграться с jabber'ом. Погуглив на предмет библиотек для работы с XMPP, для Ruby, нашел XMPP4R, которая оказалась чудовищно простой в обращении, даже с моим минимальным знанием самого ruby.

Вобщем первоначальная задача достаточно банальна - на моем домашнем сервере крутится некий сервис, который сам по себе не очень стабилен, и иногда требует перезапуска - фактически чего-то типа /etc/init.d/servicename restart. Обычно я логинюсь туда по ssh, делаю это черное дело, и до следующего раза(условие рестарта достаточно призрачное - поэтому заскриптовать делать это автоматически не получится). Хочется чтобы я мог сказать jabber боту, который будет запущен с того сервера - restart, а он уже сам запустит нужные команды для переазпуска. С помощью google и ruby у мну получился такой маленький бот, который способен посмотреть что ему ввели, и в зависимости от правильности ввода, или выполнить то что требуется, или послать нафиг :) В качестве примера здесь будет не рестарт сервиса, а выполнение команды dig, в случае если вы напишите что-то похожее на имя домена, или же предложение ввести имя домена, если то что вы ввели слабо походит на домен. Возможно кому-то будет полезно :)



#!/usr/bin/ruby
require 'xmpp4r/client'
include Jabber

def usage
puts "Usage: " $0 " jid password destination-jid"
exit
end

def reconnect(cl,password)
cl.connect
cl.auth(password)
cl.send(Presence.new.set_type(:available))
puts `date`
end

#Jabber::debug = true

jid = ARGV[0]
password = ARGV[1]
to = ARGV[2]
unless jid && password
usage
end

cl = Client::new(jid "/BOT-MODE")
cl.connect
cl.auth(password)

cl.on_exception { sleep 5; reconnect(cl,password) }

cl.send(Presence.new.set_type(:available))

if to
subject = "XMPP4R test"
body = "Give me a domain name and I will do some magic for you"
m_to = Message::new(to, body).set_type(:normal).set_id('1').set_subject(subject)
cl.send m_to
end

mainthread = Thread.current

cl.add_message_callback do |m|
if m.body =~ /^[a-zA-Z0-9.-] \.[a-zA-Z.]{2,5}$/
dig = "\n"
dig = `dig noall answer #{m.body}`
dig = "===========\n"
dig = `dig noall answer NS #{m.body}`
dig = "===========\n"
dig = `dig noall answer MX #{m.body}`
dig = "===========\n"

m_to = Message::new(m.from, dig ).set_type(:normal).set_id('1').set_subject(subject)
cl.send m_to

elsif m.body != nil
body = "Give me a domain name and I will do some magic for you"
m_to = Message::new(m.from, body).set_type(:normal).set_id('1').set_subject(subject)
cl.send m_to
puts "response sended to #{m.from}"
end
puts m.from,m.type, m.body if m.body != nil
end

### keep alive
Thread.new do
while true do
if cl.is_connected?
cl.send(Presence.new)
else
reconnect(cl,password)
end
sleep 30;
end
end
Thread.stop
cl.close

Готовый файл можно стянуть отсюда: http://muaddeep.googlepages.com/jabber-cl.rb, комменты от знатоков приветствуются. На что здесь нужно обратить внимание:
  • если вы настраиваете add_message_callback так чтобы отвечать всем кто вам что-либо напишет, то нужно обратить внимание что ответы эти будут идти и на всякие presense notifications, и есть риск заспамить всех кто в данный момент в он-лайн. Поэтому нужно проверять что в message body точно что-то есть.
  • Полезной является практика напоминать серверу о своем присутствии(иначе соединение разрывается, а скрипт сегфолтится), а так же автоматически переконнекчивать бота в случае если соединение все-таки разорвалось(на домашнем jabber-сервере соединение разорвалось где-то через час, на "настоящих" все происходит гораздо быстрее).
  • Статьи рекомендованные на http://home.gna.org/xmpp4r/ и примеры которые идут с пакетом, многое объясняют :)
Всех кого это касается с Днем Независимости! До новых встречЪ.:)

2 комментария:

A.I. комментирует...

Я бы не писал «include Jabber», так как тут это лишь сокращает 1 строку:

cl = Client::new(jid "/BOT-MODE") был бы
cl = Jabber::Client::new(jid "/BOT-MODE")

Второй случай как-то более понятен, сразу видно что за клиент :).

diesel комментирует...

Да, точно, можно без этого инклуда, правда строк там чуть больше - еще Jabber::Message, в примерах на которые я смотрел - было с инклудом, я так и списал :)