Jabber bot with Ruby

Posted by Dan Sosedoff on May 20, 2009

Ruby has very powerful tools to create random jabber bots with XMPP protocol. In this article i`ll show just a small sample with a few commands available.

I got xmpp4r-simple library from Google Code page – http://code.google.com/p/xmpp4r-simple/. Its kinda old, not updated since 2006, but the sources are really easy to read and understand, and probably modify.

First, we need to install dependencies (gems):

$ gem install xmpp4r
$ gem install xmpp4r-simple

And now, the actual ruby code:

#!/usr/bin/ruby
require 'rubygems'
require 'xmpp4r'
require 'xmpp4r-simple'
 
$jabber_login = "YOUR_JABBER_ID_HERE" # test@jabber.org
$jabber_password = "YOUR_PASSWORD_HERE" 
$client = nil
 
# create jabber connection
def jabber_connect()
  begin
    conn = Jabber::Simple.new($jabber_login,$jabber_password)
    return conn
  rescue
    return nil
  end
end
 
# send message to jabber client
def jabber_respond(to, msg)
  $client.deliver(to,msg,:chat)	
end
 
# get sender jabber id
def jabber_get_jid(str)
  matches = str.match(/([a-z\d_.\-]{1,32})@([a-z\d.-]{1,32})\//i)
  return "#{matches[1]}@#{matches[2]}"
end
 
# ------------------------------------------------------------------- #
 
# send server time
def app_time(jid)
  jabber_respond(jid, "Server time is: #{Time.now}")
end
 
# send some 'help' :)
def app_help(jid)
  jabber_respond(jid, "Nobody can help you. You`re alone.")
end
 
# process received message
def app_parse_msg(jid, msg)
  cmd = msg.body.strip
  begin
    case cmd
      when /^help$/i then app_help(jid)
      when /^time$/i then app_time(jid)
      when /^jid$/i then jabber_respond(jid, "Your jabber id: #{jid}")
      else
        jabber_respond(jid, "Unknown command. Try something different.")
    end
  rescue Exception => ex
    jabber_respond(jid, "SYSTEM_ERROR: #{ex}")
  end
end
 
# ------------------------------------------------------------------- #
 
puts "Connecting to jabber server..."
$client = jabber_connect()
if $client
  puts "Connected. Waiting for messages..."
  loop do
    $client.received_messages do |message|
      jid = jabber_get_jid(message.from.to_s)
      puts "Received message from #{jid}: #{message.body}"
      app_parse_msg(jid, message)
    end
    sleep 0.1
  end
else
  puts "Cannot connect. Please try again later."
end

I think, the comments in source code are enough to understand what this bot supposed to do. There are 3 commands available: ‘help’, ‘time’, ‘jid’

Download script here – http://files.sosedoff.com/204ab61c/

WebDAV client in ruby 1

Posted by Dan Sosedoff on May 02, 2009

Here is a simple example how to make native WebDAV client with Ruby sockets. No additional gems or extensions needed – just all basic classes.

class WebDAV
	attr_reader :host, :port, :protocol, :chunk_size
	@socket = nil
 
	def initialize(host,port=80,protocol='HTTP/1.1',chunk=8096)
		@host = host.to_s
		@port = port.to_i
		@protocol = protocol
		@chunk_size = chunk.to_i
	end
 
	def build_header(method, path, content_length=nil)
		header = "#{method} #{path} #{@protocol} \r\n"
		header += "Content-Length: #{content_length}\r\n" if !content_length.nil?
		header += "Host: #{@host}\r\n"
		header += "Connection: close\r\n\r\n"
		return header
	end
 
	def request(method, path)
		open
		header = build_header(method, path)
		if @socket.write(header) == header.length then
			return @socket.gets.split[1]
		end
	end
 
	def delete(path)
		request('DELETE', path)
	end
 
	def head(path)
		request('HEAD', path)
	end
 
	def mkcol(path)
		request('MKCOL', path)
	end
 
	def put(path, localfile, auto_head=true)
		if !File.exists?(localfile) || !File.readable?(localfile)
			raise "File not exists or not accessible for reading!"
		end
 
		open
 
		datalen = File.size(localfile)
		header = build_header('PUT', path, datalen)
 
		begin
			if @socket.write(header) == header.length then
				written = 0
				File.open(localfile,'r') do |f| 
					until f.eof? do
						written += @socket.write(f.read(@chunk_size))
					end
				end
 
				if written == datalen
					close
					if !auto_head
						return true
					else
						return head(path)
					end
				end
			end
		rescue Exception => e
			puts e
			return false
		end
	end
 
	def open
		begin 
			@socket = TCPSocket.open(@host,@port)
			return true
		rescue Exception => e
			puts e
			return false
		end
	end
 
	def close
		begin
			return @socket.close
		rescue 
			return false
		end
	end
end

This class supports only basic http/dav methods (PUT, DELETE, MKCOL, HEAD) and can be extended very easily and designed to work with all files, reading them by small chunks (default is 8096 bytes).
Im using this class sometimes with nginx.

Deps:

require 'socket'
require 'digest'

Usage:

# create connection
conn = WebDAV.new('your.host.com')
 
# upload file (without autocheck), return true/false value
result = conn.put('/test.mp3','/home/.../..../..../file.mp3', false)
 
# upload file with autocheck, returns http response code (201, 404, ... ) so you`ll know what exactly happened
result = conn.put('/test2.mp3','/home/.../file.mp3')

Also, here is a wrapper class to produce MD5, SHA1 file hashes that supports big files.

class FileHash 
	def self.md5(path)
		d = Digest::MD5.new
		File.open(path,'r') do |f| 
			d.update(f.read(8192)) until f.eof?
		end
		return d.hexdigest
	end
 
	def self.sha1(path)
		d = Digest::SHA1.new
		File.open(path,'r') do |f| 
			d.update(f.read(8192)) until f.eof?
		end
		return d.hexdigest
	end
end

Usage:

FileHash.md5('/path/to/file')
FileHash.sha1('/path/to/file')

This webdav class not pretending to be stable in production environment, but can be useful for some “one-time” tasks with less code.