examples/mcabberrc.lua
author Myhailo Danylenko <isbear@ukrpost.net>
Tue, 24 Feb 2009 09:14:00 +0200
changeset 6 90dceae3ed1f
parent 5 cba039bd6f13
child 7 eb6d89bf1fbf
permissions -rw-r--r--
Hooks unification * hook naming scheme unification * one handler for all * handler name from option * multiple transports * dopath returns error message



--[[

DESCRIPTION

This is a demo config file to show, what you can do with lua.

Feature list:
XEP-0004 Forms parsing and filling
XEP-0030 Info/items discovery requests (mcabber already can do replies)
XEP-0047 In-Bound Byte Streams (sending, receiving, saving with specified name, rejecting)
XEP-0077 In-Band Registration (only registration, but with data forms too)
XEP-0146 Remote commands requests
Jobs (actions, fired when some event occurs, now in one file, updated on exit)
MPD status polling (can be turned off)
Beep on all messages, even on chatroom ones
Url saving to file (for urlview)
Transported buddies availability indication
Actions on multiple marked buddies
Fallback commands (localized also! :) )
Help for fallback commands (well, I know, that it can be done with mcabber's help system, but it requires access to system files...)
All features have native mcabber interface

REQUIREMENTS

liblua-loudmouth (lm.lua and loudmouth.so)
liblua-socket - mpd

NOTES

Most hooks get one incoming parameter - hash table with some fields in it.
This allows further extending and adding arguments without breaking
previous implementations.

Ibb uses own iq handler. This is the laziest way to implement this.

BUILTINS

print (global) - prints to log
dofile (global) - loads lua file from default mcabber location

main methods:
- run           - run literal mcabber command
- beep          - beep
- log           - print to log w/specified priority
- print_info    - print info into specified buffer
- config_file   - format full file name from relative to mcabber's config dir
- status        - get current user status and message
- roster        - get list of roster jids (rooms, buddies and agents)
- current_buddy - get jid of current buddy
- buddy_info    - get table with info about jid and its resources
- connection    - get lightuserdata for mcabber's lm connection
- timer         - run function periodically
- bgread        - run command and read it's output in background
- add_feature   - add string to feature list (for disco#info)
- del_feature   - delete string from feature list
- add_command   - adds mcabber command
- del_command   - remove mcabber command

--]]

-- This is a hack to allow loading of lm.lua and loudmouth.so from ~/.mcabber
-- instead of installing them system-wide
package.path = main.config_file ( '?.lua' ) .. ';' .. package.path
package.cpath = main.config_file ( '?.so' ) .. ';' .. package.cpath

require 'lm'

-- OPTIONS, COMMON SUPPORT ROUTINES

url_file       = main.config_file ( 'urls.log' )
transport_jids = { 'icq.jabber.kiev.ua', 'mrim.unixzone.org.ua' }
beep_enable    = false

-- XXX: to C?
char2status = {
	f = 'free',
	o = 'online',
	a = 'away',
	d = 'dnd',
	n = 'notavail',
	i = 'invisible',
	['_'] = 'offline',
	['?'] = 'message',
}

function shell_escape ( str )
	if str then
		return "'" .. str:gsub ( "'", "'\\''" ) .. "'"
	else
		return "''"
	end
end

-- This is for debugging purposes, for real reloading need to quote and bracket keys.
function table_to_string ( tab, pre )
	local prefix = pre or ""
	local tbls, jk = "", ""

	for key, val in pairs ( tab ) do
		if type ( val ) == 'table' then
			tbls = string.format ( "%s  %s%s = %s,\n", tbls, prefix, tostring(key), table_to_string ( val, "  " .. prefix ) )
		else
			jk = string.format ( "%s %s = %q,", jk, tostring(key), tostring(val) )
		end
	end

	if tbls == "" then
		return string.format ( "{%s }", jk:sub ( 1, -2 ) )
	else
		return string.format ( "{%s\n%s%s}", jk, tbls, prefix )
	end
end

-- XXX to C?
function full_current_jid ()
	local jid = main.current_buddy ()
	if jid then
		local info = main.buddy_info ( jid )
		local prio, resource = 0
		for res, par in pairs ( info.resources ) do
			if prio <= par.priority then
				resource = res
				prio = par.priority
			end
		end
		if resource then
			return jid .. '/' .. resource
		else
			return jid
		end
	else
		return nil
	end
end

function online ( jid )
	local info = main.buddy_info ( jid )
	if not info then
		return false
	end
	for resource, params in pairs ( info.resources ) do
		if params.status ~= '_' then
			return true
		end
	end
	return false
end

function yesno ( value )
	if value == 'enable' or value == 'yes' or value == 'true' or value == 'on' or value == true then
		return true
	elseif value == 'disable' or value == 'no' or value == 'false' or value == 'off' or value == false then
		return false
	else
		return nil
	end
end

-- FIXME: eats spaces
function parse_args ( args )
	local ret = {}
	local still_opts = true
	local optname
	local option = false
	for word in args:gmatch ( "%S+" ) do
		if still_opts and not option and word:sub ( 1, 1 ) == '-' then
			option = true
			optname = word:sub ( 2 )
		elseif option then
			ret[optname] = word
			option = false
		else
			still_opts = false
			table.insert ( ret, word )
		end
	end
	return ret
end

-- COMMANDS

-- Help strings should not contain command, only arguments. This is necessary to support soft aliases.
commands_help = {
	file      = "filename\n\nSends file as a message. Just shorthand.",
	s         = "status [message]\n\nSets your status, but takes into account mpd (if enabled).",
	beep      = "[enable|disable|on|off|yes|no|true|false]\n\nEnables or disables beeping on all messages.\nIf state is omitted, prints current state.",
	cmd       = "shell_command\n\nRuns shell command in background and sends output to current buddy.\nWorks asynchroneously, and may break long output in the middle of line",
	exthelp   = "[command]\n\nPrints help for a given command, or list of available help topics.",
	reload    = "\n\nJust a shorthand to reload lua config file. Note, that for now this discards all changes to configuration, open forms, transferred files.",
	['join!'] = "\n\nForcibly joins to current buddy. Just saves you typing of full room name (that can be quite long) in a case of a non-bookmarked rooms.",
	count     = "\n\nPrints number of resources of current buddy. Useful to determine member count of large room."
}

main.add_command ( "lua",
	function ( args )
		assert ( loadstring ( args ) ) ()
	end )
main.add_command ( 'file',
	function ( args )
		main.run ( 'say_to -f ' .. args .. ' .' )
	end )
main.add_command ( 's',
	function ( args )
		main.run ( ('status %s %s'):format ( args, mpd_getstatus () ) )
	end )
main.add_command ( 'beep',
	function ( args )
		local enable = yesno ( args )
		if enable == nil then
			if beep_enable then
				print ( "Beep on message is enabled" )
			else
				print ( "Beep on message is disabled" )
			end
		else
			beep_enable = enable
		end
	end )
main.add_command ( 'cmd',
	function ( args )
		local to = main.current_buddy ()
		main.run ( ('send_to -q %q $ %s'):format ( to, args ) )
		main.bgread ( args,
			function ( data )
				if data then
					main.run ( ('send_to -q %q %s'):format ( to, data ) )
					return true
				else
					return false
				end
			end )
	end )
main.add_command ( 'exthelp',
	function ( args )
		if commands_help[args] then
			print ( "\n /" .. args .. ' ' .. commands_help[args] )
		else
			print ( "No help for this command." )
			list = "Help available for commands: "
			for k in pairs (commands_help) do
				list = list .. k .. ', '
			end
			print ( list:sub ( 1, -3 ) )
			print ( "For built-in mcabber commands see /help" )
		end
	end )
main.add_command ( 'reload',
	function ( args )
		dofile ( main.config_file ( 'mcabberrc.lua' ) )
	end )
main.add_command ( 'join!',
	function ( args )
		main.run ( 'room join ' .. main.current_buddy () )
	end )
main.add_command ( 'count',
	function ( args )
		local count = 0
		for resource in pairs ( main.buddy_info ( main.current_buddy () ).resources ) do
			count = count + 1
		end
		print ( "Resource count: " .. count )
	end )

for k, arg in ipairs ( { ')', '/', '(', 'D', '-/', 'S', '1', ']', '[' } ) do
	main.add_command ( arg,
		function ( args )
			main.run ( 'say :' .. arg .. ' ' .. args )
		end )
end

-- MARKING

dopath 'marking'

-- MPD

dopath 'mpd'

-- FORMS (XEP-0004)

dopath 'xep0004'

-- DISCO (XEP-0030)

dopath 'xep0030'

-- IBB (XEP-0047)

dopath 'xep0047'

-- IN-BAND REGISTRATION (XEP-0077)

dopath 'xep0077'

-- REMOTE CONTROLLING CLIENTS (XEP-0146)

dopath 'xep0146'

-- JOBS

delayed_jobs = {}

dopath 'jobs.lua'

function save_jobs ()
	local h = io.open ( main.config_file ( 'jobs.lua' ), "w" )
	if not h then
		print ( 'Cannot open jobs file for writing!' )
		return
	end
	h:write ( "-- This is autogenerated file, do not edit it manually\n\ndelayed_jobs = {\n" );
	for jid, more in pairs ( delayed_jobs ) do
		h:write ( string.format ( "\t[%q] = {\n", jid ) )
		for status, action in pairs ( more ) do
			if action then -- remove fired jobs
				h:write ( string.format ( "\t\t[%q] = %q,\n", status, action ) )
			end
		end
		h:write ( "\t},\n" )
	end
	h:write ( "}\n" )
	h:close ()
end

main.add_command ( 'delay',
	function ( args )
		args = parse_args ( args )
		local who
		if args.t then
			who = args.t
			args.t = nil
		else
			who = main.current_buddy ()
		end
		local stat = args[1]
		args[1] = nil
		delayed_jobs[who] = { }
		delayed_jobs[who][stat] =
			function ()
				main.run ( 'say_to -q ' .. who .. ' ' .. rebuild_args_string ( args ) )
			end
	end )

main.add_command ( 'job',
	function ( args )
		local action, jid, stat = args:match ( "(%w+)%s+(%w+)%s+(%w)" )
		if action == 'del' then
			delayed_jobs[jid][stat] = nil
		else
			print ( 'List of jobs:' )
			for jid, jobs in pairs ( delayed_jobs ) do
				for status in pairs ( jobs ) do
					print ( ' - ' .. jid .. ' -> ' .. status )
				end
			end
		end
	end )

commands_help['delay'] = "[-t target_jid] status_letter message\n\nDelays sending a message to target jid (or current buddy) until it switches to specified status."
commands_help['job'] = "[del jid status_letter]\n\nLists available jobs or deletes specified one."

-- HOOKS

ibb_handler_registered = false

-- Hard hooks, implemented in C

-- hook:
-- - hook-message-in
--   jid
--   groupchat
--   message
-- - hook-message-out
--   jid
--   message
-- - hook-status-change
--   jid
--   resource
--   new_status
--   old_status
--   message
-- - hook-my-status-change
--   new_status
--   message
-- - hook-post-connect
-- - hook-pre-disconnect
-- - hook-start
-- - hook-quit
function hook_handler ( args )
	if args.hook == 'hook-message-in' then

		-- beep on ALL messages, no matter, is it chat or something else.
		if beep_enable then
			main.beep ()
		end
	
		-- save urls to file from where urlview can get them...
		for url in args.message:gmatch ( "https?://[%w%p]+" ) do
			fd = io.open ( url_file, "a" )
			if fd then
				fd:write ( url .. "\n" )
				fd:close ()
			else
				print 'Cannot open urls log file'
			end
		end

	elseif args.hook == 'hook-status-change' then

		-- delayed actions
		if delayed_jobs[args.jid] and delayed_jobs[args.jid][args.new_status] then
			delayed_jobs[args.jid][args.new_status] ()
			delayed_jobs[args.jid][args.new_status] = nil
		end
		
		-- transported buddies availability indication
		for k, jid in pairs ( transport_jids ) do
			if args.jid == jid then
				if args.new_status == '_' then
					main.run ( ("color roster * *@%s red"):format ( jid ) )
					main.run ( ("color roster dn_? *@%s red"):format ( jid ) )
				else
					main.run ( ("color roster * *@%s white"):format ( jid ) )
					main.run ( ("color roster dn_? *@%s brightblack"):format ( jid ) )
				end
			end
		end

	elseif args.hook == 'hook-post-connect' then
	
		if mpd_enabled then
			mpd_callback ()
		end
	
		-- FIXME
		if not ibb_handler_registered then
			lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq', 'normal' )
			main.add_feature ( 'http://jabber.org/protocol/ibb' )
			ibb_handler_registered = true
		end

	elseif args.hook == 'hook-start' then

		-- XXX: for some reason, after module reloading, this is not working properly.
		for k, jid in pairs ( transport_jids ) do
			if not online ( jid ) then
				main.run ( ("color roster * *@%s red"):format ( jid ) )
				main.run ( ("color roster dn_? *@%s red"):format ( jid ) )
			else
				main.run ( ("color roster * *@%s white"):format ( jid ) )
				main.run ( ("color roster dn_? *@%s brightblack"):format ( jid ) )
			end
		end

	elseif args.hook == 'hook-quit' then

		save_jobs ()

		-- FIXME
		if ibb_handler_registered then
			lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq' )
		end

	end
end


-- The End -- vim: se ts=4: --