scripts/mcabberrc.lua
changeset 4 bca17e4a9851
equal deleted inserted replaced
3:a5f864d4207f 4:bca17e4a9851
       
     1 
       
     2 
       
     3 --[[
       
     4 
       
     5 DESCRIPTION
       
     6 
       
     7 This is a demo config file to show, what you can do with lua.
       
     8 
       
     9 Feature list:
       
    10 XEP-0004 Forms parsing and filling
       
    11 XEP-0030 Info/items discovery requests (mcabber already can do replies)
       
    12 XEP-0047 In-Bound Byte Streams (sending, receiving, saving with specified name, rejecting)
       
    13 XEP-0077 In-Band Registration (only registration, but with data forms too)
       
    14 XEP-0146 Remote commands requests
       
    15 Jobs (actions, fired when some event occurs, now in one file, updated on exit)
       
    16 MPD status polling (can be turned off)
       
    17 Beep on all messages, even on chatroom ones
       
    18 Url saving to file (for urlview)
       
    19 Transported buddies availability indication
       
    20 Actions on multiple marked buddies
       
    21 Fallback commands (localized also! :) )
       
    22 Help for fallback commands (well, I know, that it can be done with mcabber's help system, but it requires access to system files...)
       
    23 All features have native mcabber interface
       
    24 
       
    25 REQUIREMENTS
       
    26 
       
    27 liblua-loudmouth (lm.lua and loudmouth.so)
       
    28 liblua-socket - mpd
       
    29 
       
    30 NOTES
       
    31 
       
    32 Most hooks get one incoming parameter - hash table with some fields in it.
       
    33 This allows further extending and adding arguments without breaking
       
    34 previous implementations.
       
    35 
       
    36 Ibb uses own iq handler. This is the laziest way to implement this.
       
    37 
       
    38 BUILTINS
       
    39 
       
    40 print (global) - prints to log
       
    41 dofile (global) - loads lua file from default mcabber location
       
    42 
       
    43 main methods:
       
    44 - run           - run literal mcabber command
       
    45 - beep          - beep
       
    46 - log           - print to log w/specified priority
       
    47 - print_info    - print info into specified buffer
       
    48 - config_file   - format full file name from relative to mcabber's config dir
       
    49 - status        - get current user status and message
       
    50 - roster        - get list of roster jids (rooms, buddies and agents)
       
    51 - current_buddy - get jid of current buddy
       
    52 - buddy_info    - get table with info about jid and its resources
       
    53 - connection    - get lightuserdata for mcabber's lm connection
       
    54 - timer         - run function periodically
       
    55 - bgread        - run command and read it's output in background
       
    56 - add_feature   - add string to feature list (for disco#info)
       
    57 - del_feature   - delete string from feature list
       
    58 - add_command   - adds mcabber command
       
    59 - del_command   - remove mcabber command
       
    60 
       
    61 --]]
       
    62 
       
    63 -- This is a hack to allow loading of lm.lua and loudmouth.so from ~/.mcabber
       
    64 -- instead of installing them system-wide
       
    65 package.path = main.config_file ( '?.lua' ) .. ';' .. package.path
       
    66 package.cpath = main.config_file ( '?.so' ) .. ';' .. package.cpath
       
    67 
       
    68 require 'lm'
       
    69 
       
    70 -- OPTIONS, COMMON SUPPORT ROUTINES
       
    71 
       
    72 url_file      = main.config_file ( 'urls.log' )
       
    73 transport_jid = 'icq.jabber.kiev.ua' -- TODO: allow multiple transports
       
    74 beep_enable   = false
       
    75 
       
    76 -- XXX: to C?
       
    77 char2status = {
       
    78 	f = 'free',
       
    79 	o = 'online',
       
    80 	a = 'away',
       
    81 	d = 'dnd',
       
    82 	n = 'notavail',
       
    83 	i = 'invisible',
       
    84 	['_'] = 'offline',
       
    85 	['?'] = 'message',
       
    86 }
       
    87 
       
    88 function shell_escape ( str )
       
    89 	if str then
       
    90 		return "'" .. str:gsub ( "'", "'\\''" ) .. "'"
       
    91 	else
       
    92 		return "''"
       
    93 	end
       
    94 end
       
    95 
       
    96 -- This is for debugging purposes, for real reloading need to quote and bracket keys.
       
    97 function table_to_string ( tab, pre )
       
    98 	local prefix = pre or ""
       
    99 	local tbls, jk = "", ""
       
   100 
       
   101 	for key, val in pairs ( tab ) do
       
   102 		if type ( val ) == 'table' then
       
   103 			tbls = string.format ( "%s  %s%s = %s,\n", tbls, prefix, tostring(key), table_to_string ( val, "  " .. prefix ) )
       
   104 		else
       
   105 			jk = string.format ( "%s %s = %q,", jk, tostring(key), tostring(val) )
       
   106 		end
       
   107 	end
       
   108 
       
   109 	if tbls == "" then
       
   110 		return string.format ( "{%s }", jk:sub ( 1, -2 ) )
       
   111 	else
       
   112 		return string.format ( "{%s\n%s%s}", jk, tbls, prefix )
       
   113 	end
       
   114 end
       
   115 
       
   116 -- XXX to C?
       
   117 function full_current_jid ()
       
   118 	local jid = main.current_buddy ()
       
   119 	if jid then
       
   120 		local info = main.buddy_info ( jid )
       
   121 		local prio, resource = 0
       
   122 		for res, par in pairs ( info.resources ) do
       
   123 			if prio <= par.priority then
       
   124 				resource = res
       
   125 				prio = par.priority
       
   126 			end
       
   127 		end
       
   128 		if resource then
       
   129 			return jid .. '/' .. resource
       
   130 		else
       
   131 			return jid
       
   132 		end
       
   133 	else
       
   134 		return nil
       
   135 	end
       
   136 end
       
   137 
       
   138 -- COMMANDS
       
   139 
       
   140 function yesno ( value )
       
   141 	if value == 'enable' or value == 'yes' or value == 'true' or value == 'on' or value == true then
       
   142 		return true
       
   143 	elseif value == 'disable' or value == 'no' or value == 'false' or value == 'off' or value == false then
       
   144 		return false
       
   145 	else
       
   146 		return nil
       
   147 	end
       
   148 end
       
   149 
       
   150 -- FIXME: eats spaces
       
   151 function parse_args ( args )
       
   152 	local ret = {}
       
   153 	local still_opts = true
       
   154 	local optname
       
   155 	local option = false
       
   156 	for word in args:gmatch ( "%S+" ) do
       
   157 		if still_opts and not option and word:sub ( 1, 1 ) == '-' then
       
   158 			option = true
       
   159 			optname = word:sub ( 2 )
       
   160 		elseif option then
       
   161 			ret[optname] = word
       
   162 			option = false
       
   163 		else
       
   164 			still_opts = false
       
   165 			table.insert ( ret, word )
       
   166 		end
       
   167 	end
       
   168 	return ret
       
   169 end
       
   170 
       
   171 -- Help strings should not contain command, only arguments. This is necessary to support soft aliases.
       
   172 commands_help = {
       
   173 	file      = "filename\n\nSends file as a message. Just shorthand.",
       
   174 	s         = "status [message]\n\nSets your status, but takes into account mpd (if enabled).",
       
   175 	beep      = "[enable|disable|on|off|yes|no|true|false]\n\nEnables or disables beeping on all messages.\nIf state is omitted, prints current state.",
       
   176 	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",
       
   177 	exthelp   = "[command]\n\nPrints help for a given command, or list of available help topics.",
       
   178 	reload    = "\n\nJust a shorthand to reload lua config file. Note, that for now this discards all changes to configuration, open forms, transferred files.",
       
   179 	['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.",
       
   180 	count     = "\n\nPrints number of resources of current buddy. Useful to determine member count of large room."
       
   181 }
       
   182 
       
   183 main.add_command ( "lua",
       
   184 	function ( args )
       
   185 		assert ( loadstring ( args ) ) ()
       
   186 	end )
       
   187 main.add_command ( 'file',
       
   188 	function ( args )
       
   189 		main.run ( 'say_to -f ' .. args .. ' .' )
       
   190 	end )
       
   191 main.add_command ( 's',
       
   192 	function ( args )
       
   193 		main.run ( ('status %s %s'):format ( args, mpd_getstatus () ) )
       
   194 	end )
       
   195 main.add_command ( 'beep',
       
   196 	function ( args )
       
   197 		local enable = yesno ( args )
       
   198 		if enable == nil then
       
   199 			if beep_enable then
       
   200 				print ( "Beep on message is enabled" )
       
   201 			else
       
   202 				print ( "Beep on message is disabled" )
       
   203 			end
       
   204 		else
       
   205 			beep_enable = enable
       
   206 		end
       
   207 	end )
       
   208 main.add_command ( 'cmd',
       
   209 	function ( args )
       
   210 		local to = main.current_buddy ()
       
   211 		main.run ( ('send_to -q %q $ %s'):format ( to, args ) )
       
   212 		main.bgread ( args,
       
   213 			function ( data )
       
   214 				if data then
       
   215 					main.run ( ('send_to -q %q %s'):format ( to, data ) )
       
   216 					return true
       
   217 				else
       
   218 					return false
       
   219 				end
       
   220 			end )
       
   221 	end )
       
   222 main.add_command ( 'exthelp',
       
   223 	function ( args )
       
   224 		if commands_help[args] then
       
   225 			print ( "\n /" .. args .. ' ' .. commands_help[args] )
       
   226 		else
       
   227 			print ( "No help for this command." )
       
   228 			list = "Help available for commands: "
       
   229 			for k in pairs (commands_help) do
       
   230 				list = list .. k .. ', '
       
   231 			end
       
   232 			print ( list:sub ( 1, -3 ) )
       
   233 			print ( "For built-in mcabber commands see /help" )
       
   234 		end
       
   235 	end )
       
   236 main.add_command ( 'reload',
       
   237 	function ( args )
       
   238 		dofile ( main.config_file ( 'mcabberrc.lua' ) )
       
   239 	end )
       
   240 main.add_command ( 'join!',
       
   241 	function ( args )
       
   242 		main.run ( 'room join ' .. main.current_buddy () )
       
   243 	end )
       
   244 main.add_command ( 'count',
       
   245 	function ( args )
       
   246 		local count = 0
       
   247 		for resource in pairs ( main.buddy_info ( main.current_buddy () ).resources ) do
       
   248 			count = count + 1
       
   249 		end
       
   250 		print ( "Resource count: " .. count )
       
   251 	end )
       
   252 
       
   253 for k, arg in ipairs ( { ')', '/', '(', 'D', '-/', 'S', '1', ']', '[' } ) do
       
   254 	main.add_command ( arg,
       
   255 		function ( args )
       
   256 			main.run ( 'say :' .. arg .. ' ' .. args )
       
   257 		end )
       
   258 end
       
   259 
       
   260 -- MARKING
       
   261 
       
   262 dopath 'marking'
       
   263 
       
   264 -- MPD
       
   265 
       
   266 dopath 'mpd'
       
   267 
       
   268 -- FORMS (XEP-0004)
       
   269 
       
   270 dopath 'xep0004'
       
   271 
       
   272 -- DISCO (XEP-0030)
       
   273 
       
   274 dopath 'xep0030'
       
   275 
       
   276 -- IBB (XEP-0047)
       
   277 
       
   278 dopath 'xep0047'
       
   279 
       
   280 -- IN-BAND REGISTRATION (XEP-0077)
       
   281 
       
   282 dopath 'xep0077'
       
   283 
       
   284 -- REMOTE CONTROLLING CLIENTS (XEP-0146)
       
   285 
       
   286 dopath 'xep0146'
       
   287 
       
   288 -- JOBS
       
   289 
       
   290 delayed_jobs = {}
       
   291 
       
   292 -- FIXME: do only if it exists
       
   293 dopath 'jobs.lua'
       
   294 
       
   295 function save_jobs ()
       
   296 	local h = io.open ( main.config_file ( 'jobs.lua' ), "w" )
       
   297 	if not h then
       
   298 		print ( 'Cannot open jobs file for writing!' )
       
   299 		return
       
   300 	end
       
   301 	h:write ( "-- This is autogenerated file, do not edit it manually\n\ndelayed_jobs = {\n" );
       
   302 	for jid, more in pairs ( delayed_jobs ) do
       
   303 		h:write ( string.format ( "\t[%q] = {\n", jid ) )
       
   304 		for status, action in pairs ( more ) do
       
   305 			if action then -- remove fired jobs
       
   306 				h:write ( string.format ( "\t\t[%q] = %q,\n", status, action ) )
       
   307 			end
       
   308 		end
       
   309 		h:write ( "\t},\n" )
       
   310 	end
       
   311 	h:write ( "}\n" )
       
   312 	h:close ()
       
   313 end
       
   314 
       
   315 main.add_command ( 'delay',
       
   316 	function ( args )
       
   317 		args = parse_args ( args )
       
   318 		local who
       
   319 		if args.t then
       
   320 			who = args.t
       
   321 			args.t = nil
       
   322 		else
       
   323 			who = main.current_buddy ()
       
   324 		end
       
   325 		local stat = args[1]
       
   326 		args[1] = nil
       
   327 		delayed_jobs[who] = { }
       
   328 		delayed_jobs[who][stat] =
       
   329 			function ()
       
   330 				main.run ( 'say_to -q ' .. who .. ' ' .. rebuild_args_string ( args ) )
       
   331 			end
       
   332 	end )
       
   333 
       
   334 main.add_command ( 'job',
       
   335 	function ( args )
       
   336 		local action, jid, stat = args:match ( "(%w+)%s+(%w+)%s+(%w)" )
       
   337 		if action == 'del' then
       
   338 			delayed_jobs[jid][stat] = nil
       
   339 		else
       
   340 			print ( 'List of jobs:' )
       
   341 			for jid, jobs in pairs ( delayed_jobs ) do
       
   342 				for status in pairs ( jobs ) do
       
   343 					print ( ' - ' .. jid .. ' -> ' .. status )
       
   344 				end
       
   345 			end
       
   346 		end
       
   347 	end )
       
   348 
       
   349 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."
       
   350 commands_help['job'] = "[del jid status_letter]\n\nLists available jobs or deletes specified one."
       
   351 
       
   352 -- HOOKS
       
   353 
       
   354 ibb_handler_registered = false
       
   355 
       
   356 -- Soft hooks, implemented through mcabber options
       
   357 function hook_post_connect ()
       
   358 	main.run ( 'group fold テフノ' )
       
   359 	main.run ( 'group fold にゃ' )
       
   360 	main.run ( 'group fold にゃ - Друзі' )
       
   361 
       
   362 	main.run ( 'color muc * on' )
       
   363 
       
   364 	main.run ( ("color roster * *@%s red"):format ( transport_jid ) )
       
   365 	main.run ( ("color roster dn_? *@%s red"):format ( transport_jid ) )
       
   366 
       
   367 	if mpd_enabled then
       
   368 		mpd_callback ()
       
   369 	end
       
   370 
       
   371 	-- FIXME
       
   372 	if not ibb_handler_registered then
       
   373 		lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq', 'normal' )
       
   374 		main.add_feature ( 'http://jabber.org/protocol/ibb' )
       
   375 		ibb_handler_registered = true
       
   376 	end
       
   377 end
       
   378 
       
   379 function hook_pre_disconnect ()
       
   380 	main.run ( ("color roster * *@%s white"):format ( transport_jid ) )
       
   381 	main.run ( ("color roster dn_? *@%s brightblack"):format ( transport_jid ) )
       
   382 end
       
   383 
       
   384 -- Hard hooks, implemented in C
       
   385 
       
   386 -- hook
       
   387 -- - message_in
       
   388 --   jid
       
   389 --   groupchat
       
   390 --   message
       
   391 -- - message_out
       
   392 --   jid
       
   393 --   message
       
   394 -- - status_change
       
   395 --   jid
       
   396 --   resource
       
   397 --   new_status
       
   398 --   old_status
       
   399 --   message
       
   400 -- - my_status_change
       
   401 --   new_status
       
   402 --   message
       
   403 function hook_handler ( args )
       
   404 	if args.hook == 'message_in' then
       
   405 
       
   406 		-- beep on ALL messages, no matter, is it chat or something else.
       
   407 		if beep_enable then
       
   408 			main.beep ()
       
   409 		end
       
   410 	
       
   411 		-- save urls to file from where urlview can get them...
       
   412 		for url in args.message:gmatch ( "https?://[%w%p]+" ) do
       
   413 			fd = io.open ( url_file, "a" )
       
   414 			if fd then
       
   415 				fd:write ( url .. "\n" )
       
   416 				fd:close ()
       
   417 			else
       
   418 				print 'Cannot open urls log file'
       
   419 			end
       
   420 		end
       
   421 
       
   422 	elseif args.hook == 'status_change' then
       
   423 
       
   424 		-- delayed actions
       
   425 		if delayed_jobs[args.jid] and delayed_jobs[args.jid][args.new_status] then
       
   426 			delayed_jobs[args.jid][args.new_status] ()
       
   427 			delayed_jobs[args.jid][args.new_status] = nil
       
   428 		end
       
   429 		
       
   430 		-- transported buddies availability indication
       
   431 		if args.jid == transport_jid then
       
   432 			if args.new_status == '_' then
       
   433 				main.run ( ("color roster * *@%s red"):format ( transport_jid ) )
       
   434 				main.run ( ("color roster dn_? *@%s red"):format ( transport_jid ) )
       
   435 			else
       
   436 				main.run ( ("color roster * *@%s white"):format ( transport_jid ) )
       
   437 				main.run ( ("color roster dn_? *@%s brightblack"):format ( transport_jid ) )
       
   438 			end
       
   439 		end
       
   440 
       
   441 	end
       
   442 end
       
   443 
       
   444 -- (hook_start)
       
   445 
       
   446 function hook_quit ()
       
   447 	save_jobs ()
       
   448 
       
   449 	-- FIXME
       
   450 	if ibb_handler_registered then
       
   451 		lm.connection.bless( main.connection () ):handler ( ibb_incoming_iq_handler, 'iq' )
       
   452 	end
       
   453 end
       
   454 
       
   455 
       
   456 -- The End -- vim: se ts=4: --