lm.lua
author Myhailo Danylenko <isbear@ukrpost.net>
Fri, 20 Feb 2009 23:07:43 +0200
changeset 6 90073cbb535d
parent 0 84fdfb0344c9
child 7 5db1448eb857
permissions -rw-r--r--
Logging and chained methods


lm = require 'loudmouth'

-- argument is a table with keys,
-- corresponding to method names.
function lm.proxy.create ( a )
	if type ( a ) ~= "table" then
		error "arguments should be in a table"
	end
	local p = lm.proxy.new ()
	if a.server then
		p:server ( a.server )
	end
	if a.port then
		p:port ( a.port )
	end
	if a['type'] then
		p:type ( a['type'] )
	end
	if a.username then
		p:username ( a.username )
	end
	if a.password then
		p:password ( a.password )
	end
	return p
end

-- argument is a table with two keys:
-- callback and fingerprint
function lm.ssl.create ( a )
	if not lm.ssl.supported () then
		-- XXX
		-- error "ssl is not supported by your loudmouth library"
		return nil
	end
	local fp, cb
	local st = type ( a )
	if st == "table" then
		fp = a.fingerprint
		cb = a.callback
	elseif st == "function" then
		cb = a
	elseif st == "string" then
		fp = a
	elseif st ~= "nil" then
		error "unexpected type of argument"
	end
	if fp then
		if cb then
			return lm.ssl.new ( fp, cb )
		else
			return lm.ssl.new ( fp )
		end
	else
		if cb then
			return lm.ssl.new ( cb )
		else
			return lm.ssl.new ()
		end
	end
end

-- basically, it just provides a way
-- to initialize many parameters at once.
-- keys in a table correspond to methods
-- of connection object, except for handlers,
-- where format is {
--     "type/priority" = function/object,
--     ...
-- }
-- two extra keys - server and context.
-- ssl and proxy objects can either be objects
-- or tables, directly passed to corresponding
-- create routine.
function lm.connection.create ( a )
	local at = type (a)
	if at == "string" then
		return lm.connection.new ( a )
	elseif at == "table" then
		local server = a.server
		if not server then
			error "server name parameter required"
		end

		-- create connection object
		local c
		if a.context then
			c = lm.connection.new ( server, a.context )
		else
			c = lm.connection.new ( server )
		end

		-- connection parameters
		if a.port then
			c:port ( a.port )
		end
		if a.jid then
			c:jid ( a.jid )
		end
		if a.keep_alive_rate then
			c:keep_alive_rate ( a.keep_alive_rate )
		end

		-- proxy
		if a.proxy then
			local pt = type ( a.proxy )
			if pt == "userdata" then
				c:proxy ( a.proxy )
			else
				local proxy = lm.proxy.create ( a.proxy )
				c:proxy ( proxy )
			end
		end

		-- ssl
		if a.ssl then
			local st = type ( a.ssl )
			if st == "userdata" then
				c:ssl ( a.ssl )
			else
				local ssl = lm.ssl.create ( a.ssl )
				c:ssl ( ssl )
			end
		end

		-- disconnect callback
		if a.ondisconnect then
			c:ondisconnect ( a.ondisconnect )
		end

		-- message handlers
		if a.handlers then
			if type ( a.handlers ) ~= "table" then
				error ( "handlers parameter expected to be a table " ..
				        "of the form { \"type/priority\" = function/object }" )
			end
			for mhtype, handler in pairs ( a.handlers ) do
				local mtype, prio = mhtype:match ( "(.-)/(%d+)" )
				if not mtype then
					mtype = mhtype
					prio = 0
				else
					prio = tonumber ( prio )
				end
				c:handler ( handler, mtype, prio )
			end
		end

		return c
	else
		error ( "at least server name parameter required" )
	end
end

-- recursively fills a node, see lm.message.create
function lm.message_node.fill ( n, a )
	for name, value in pairs ( a ) do
		if type ( value ) == "table" then
			if type ( value[1] ) == "table" then
				for index, instance in ipairs ( value ) do
					lm.message_node.fill ( n:child ( name, "" ), instance )
				end
			else
				lm.message_node.fill ( n:child ( name, "" ), value )
			end
		elseif name == 1 then
			n:value ( value )
		else
			n:attribute ( name, value )
		end
	end
end

--[[
-- recursively fills a message
lm.message.create { mtype = 'iq-result', to = 'foo@bar.xyz',
	command = { xmlns = 'http://jabber.org/protocol/commands', node = 'http://jabber.org/protocol/rc#set-status', status = 'executing', sessionid = 'set-status:aaa3',
		x = { xmlns = 'jabber:x:data', type = 'form',
			title = { "Change Status" },
			instructions = { "Choose the status and status message" },
			field = {{ type = 'hidden', var = 'FORM_TYPE',
				value = { "http://jabber.org/protocol/rc" },
			},{ type = 'list-single', label = 'Status', var = 'status',
				required = { },
				value = { "online" },
				option = {{ label = 'Chat',
					value = { "chat" },
				},{ label = 'Online',
					value = { "online" },
				},{ label = 'Away',
					value = { "away" },
				},{ label = 'Extended Away',
					value = { "xa" },
				},{ label = 'Do Not Disturb',
					value = { "dnd" },
				},{ label = 'Invisible',
					value = { "invisible" },
				},{ label = 'Offline',
					value = { "offline" },
				}},
			},{ type = 'text-single', label = 'Priority', var = 'status-priority',
				value = { "5" },
			},{ type = 'text-multi', label = 'Message', var = 'status-message' }},
		},
	},
}
--]]
function lm.message.create ( a )
	if type ( a ) ~= "table" then
		error "table expected as argument"
	end
	if not a.mtype or not a.to then
		error "you must specify message type and destination"
	end
	local mtype, subtype = a.mtype:match ( "(.-)%-(.+)" )
	local m
	if not mtype then
		m = lm.message.new ( a.to, a.mtype )
	else
		m = lm.message.new ( a.to, mtype, subtype )
	end
	a.to = nil
	a.mtype = nil
	lm.message_node.fill ( m:node(), a )
	return m
end

-- TODO: multiple nodes with same name
function lm.message_node.parse ( node, r )
	local n = node:children ()
	while n do
		local name = n:name ()
		r[name] = { }
		local value = n:value ()
		if value then
			r[name][1] = value
		end
		lm.message_node.parse ( n, r[name] )
		n = n:next ()
	end
end

-- There are NO WAY to get a list of node attributes,
-- except brute force...
function lm.message.parse ( message )
	local node = message:node ()
	local mtype, subtype = message:type ()
	if subtype then
		mtype = mtype .. '-' .. subtype
	end
	local r = { mtype = mtype }
	local value = node:value ()
	if value then
		r[1] = value
	end
	lm.message_node.parse ( node, r )
	return r
end

-- the same table, as for lm.connection.create, but with few more fields:
-- ssl.validate
-- onconnect
-- onopen
-- username
-- password
-- resource
function lm.connect ( a )
	if type ( a ) ~= "table" then
		error "table expected as argument"
	end
	if a.ssl then
		if a.ssl.validate and not a.ssl.callback then
			a.ssl.callback =
				function ( obj, status )
					return false
				end
		end
	end
	local c = lm.connection.create ( a )
	c:open (
		function ( obj, status )
			if status then
				if type ( a.onopen ) == "function" then
					a.onopen ( obj )
				end
				c:authenticate ( a.username, a.password, a.resource,
					function ( obj, status )
						if type ( a.onconnect ) == "function" then
							a.onconnect ( obj )
						end
					end )
			end
		end )
	return c
end

-- vim: se ts=4: --