examples/pep.lua
author Myhailo Danylenko <isbear@ukrpost.net>
Sun, 22 Mar 2009 05:49:14 +0200
changeset 51 a95a3a73482c
parent 46 c2210e9b3e34
child 56 8561e55e0662
permissions -rw-r--r--
Pubsub uses new forms


-- PERSONAL EVENTING PROTOCOL (XEP-0163)

-- library

require 'lm'
require 'iq'
require 'mpd'

-- public

pep = {
	handlers = {},
}

function pep.publish ( conn, node, data, success, fail )
--	local bjid = conn:jid():gsub ( '/.*', '' )
	data.xmlns = 'http://jabber.org/protocol/' .. node
	iq.send ( conn, nil, 'set',
		{
			pubsub = { xmlns = 'http://jabber.org/protocol/pubsub',
				publish = { node = 'http://jabber.org/protocol/' .. node,
					item = { -- id = "current",
						[node] = data,
					},
				},
			},
--[[
			configure = {
				x = {
					field = {{ type = "hidden", var = 'FORM_TYPE',
						value = { 'http://jabber.org/protocol/pubsub#node_config' },
					},{ var = "pubsub#access_model",
						value = { 'presence' },
					}},
				},
			},
--]]
		}, success, fail )
end

-- private

-- XXX in fact, it is not a pep handler, it is pubsub handler.
--     should it go there?
local pep_handler_registered       = false
local pep_incoming_message_handler = lm.message_handler.new (
	function ( conn, mess )
		local e = mess:child ( 'event' )
		if e and e:attribute ( 'xmlns' ) == 'http://jabber.org/protocol/pubsub#event' then
			local is = e:child ( 'items' )
			if is then
				local from = mess:attribute ( 'from' )
				local node = is:attribute ( 'node' )
				local item = is:child ( 'item' )
				if item then
					local handled = true -- XXX should we do this? well, if it becomes general pubsub handler - then no.
					local n       = item:children ()
					while n do
						local xmlns = n:attribute ( 'xmlns' )
						if pep.handlers[xmlns] then
							if not pep.handlers[xmlns] ( from, node, n ) then
								handled = false
							end
						else
							handled = false
						end
						n = n:next ()
					end
					return handled
				end
			end
		end
		return false
	end )

-- mcabber

local tune_enabled = false
local mpd_pub_song = { }

-- XXX in fact, we should separate library code from client code here, but it is silly.
--     seting up four registrators, four default handlers, four handler storage variables...
--     and, in fact, we cannot do much about complex datatypes of mood and activity.
pep.handlers['http://jabber.org/protocol/tune'] =
		function ( from, node, data )
			local self = false
			if from == lm.connection.bless ( main.connection () ):jid():gsub ( '/.*', '' ) then -- o_O
				self         = true
				mpd_pub_song = { }
			end
			local item = data:children ()
			local text = ''
			while item do
				local name  = item:name ()
				local value = item:value ()
				if self then
					mpd_pub_song[name] = { value or '' }
				end
				text = ("%s\n- %s: %s"):format ( text, item:name (), item:value () or '' )
				item = item:next ()
			end
			if main.yesno ( main.option ( 'lua_pep_notification' ) ) then
				if text ~= '' then
					text = 'Now listening to:' .. text
				else
					text = 'Now not listening to anything'
				end
			end
			main.print_info ( from, text )
			return true
		end
pep.handlers['http://jabber.org/protocol/mood'] =
		function ( from, node, data )
			if not main.yesno ( main.option ( 'lua_pep_notification' ) ) then
				return true
			end
			local item = data:children ()
			local mood, desc
			while item do
				if item:name () == 'text' then
					desc = item:value ()
				else
					mood = item:name ()
					-- here we can add child elements handling (by namespace)
				end
				item = item:next ()
			end
			if mood then
				main.print_info ( from, ("Buddy's mood now %s %s"):format ( mood, desc or '' ) )
			else
				main.print_info ( from, "Buddy hides his mood" )
			end
		end
pep.handlers['http://jabber.org/protocol/activity'] =
		function ( from, node, data )
			if not main.yesno ( main.option ( 'lua_pep_notification' ) ) then
				return true
			end
			local item = data:children ()
			local activity, desc
			while item do
				if item:name () == 'text' then
					desc = item:value ()
				else
					activity = item:name ()
					local subitem = item:children ()
					if subitem then
						-- here we can check for non-standard subactivity elements,
						-- add subactivity child elements handling
						activity = ("%s: %s"):format ( activity, subitem:name () )
					end
				end
				item = item:next ()
			end
			if activity then
				main.print_info ( from, ("Now %s %s"):format ( activity, desc or '' ) )
			else
				main.print_info ( from, "Buddy hides his activity" )
			end
			return true
		end
pep.handlers['http://jabber.org/protocol/geoloc'] =
		function ( from, node, data )
			if not main.yesno ( main.option ( 'lua_pep_notification' ) ) then
				return true
			end
			local item = data:children ()
			local text = ''
			while item do
				text = ("%s\n- %s: %s"):format ( text, item:name (), item:value () or '' )
				item = item:next ()
			end
			if text ~= '' then
				text = 'Now at:' .. text
			else
				text = 'Now in unknown location'
			end
			main.print_info ( from, text )
			return true
		end

local function mpd_getstatus ()
	local status = mpd.call_command { 'status' }
	if not tune_enabled or ( status.state ~= 'play' and status.state ~= 'pause' ) then
		for k, v in pairs ( mpd_pub_song ) do -- if there is anything published, publish nothing
			return { }
		end
		return nil
	end
	
	local song      = mpd.call_command { 'currentsong' }
	local dir, file = song.file:match ( '(.+)/(.-)' )
	-- populate according to currentsong fields: artist - artist, length - time, source - album, title - title, track - id, rating - ?, uri - ?
	local ret = {
		artist = { song.artist or 'Unknown' },
		length = { song.time                },
		source = { song.album  or dir       },
		title  = { song.title  or file      },
		track  = { song.id                  },
	}

	if not song.time or song.time == '0' then -- XXX
		ret.length = nil
	end

	local modified = false
	for k, v in pairs ( ret ) do
		if not mpd_pub_song[k] or mpd_pub_song[k][1] ~= v[1] then
			modified = true
			break
		end
	end
	if not modified then
		for k, v in pairs ( mpd_pub_song ) do
			if not ret[k] or ret[k][1] ~= v[1] then
				modified = true
				break
			end
		end
	end

	if modified then
		return ret
	else
		return nil
	end
end

local function dummy ()
end

local function mpd_callback ()
	local sdata = mpd_getstatus ()
	if sdata then
		pep.publish ( lm.connection.bless ( main.connection () ), 'tune', sdata, dummy, dummy )
	end
	if tune_enabled then
		return true
	else
		return false
	end
end

-- do not call it too fast, or you end up with many daemons at once
local function enable_tune ( yn )
	if yn == nil then
		yn = true
	end
	if yn then
		if not tune_enabled then
			main.timer ( 15, mpd_callback )
			tune_enabled = true
			-- update status
		end
	else
		if tune_enabled then
			tune_enabled = false
			-- update status
		end
	end
end

main.command ( 'tune',
	function ( args )
		local enable = main.yesno ( args )
		if enable == nil then
			if tune_enabled then
				print ( "Tune notifications enabled" )
			else
				print ( "Tune notifications disabled" )
			end
		else
			enable_tune ( enable )
		end
	end, false, 'yesno' )
main.command ( 'mood',
	function ( args )
		local data = { }
		local mood, text = args[1], args[2]
		if text then
			data.text  = { text }
		end
		if mood then
			data[mood] = { }
		end
		pep.publish ( lm.connection.bless ( main.connection () ), 'mood', data, dummy, dummy )
	end, true )
main.command ( 'activity',
	function ( args )
		local data = { }
		local activity, text = args[1], args[2]
		if text then
			data.text = { text }
		end
		local act, subact = activity:match ( "(.-)%-(.+)" )
		if not act then
			act = activity
		end
		if act ~= '' then
			data[act] = { }
			if subact then
				data[act][subact] = { }
			end
		end
		pep.publish ( lm.connection.bless ( main.connection () ), 'activity', data, dummy, dummy )
	end, true )
main.command ( 'location',
	function ( args )
		local data = { }
		for key, val in pairs ( args ) do
			data[key] = { val }
		end
		pep.publish ( lm.connection.bless ( main.connection () ), 'geoloc', data, dummy, dummy )
	end, true )

commands_help['tune']     = "[enable|disable|on|off|yes|no|true|false]\n\nEnables or disables publishing of notifications about playing music in your player (currently only mpd is supported)."
commands_help['mood']     = "[mood [message]]\n\nPublishes your mood.\nNote, that for now it does not checks for mood validity, so, see xep0107 for valid moods."
commands_help['activity'] = "[activity[-specific_activity] [text]]\n\nPublishes your activity.\nNote, that for now it does not checks for activity validity, so, see xep0108 for valid activity values."
commands_help['location'] = "[-key value [-key value ...]]\n\nPublishes your current geolocation.\nValid keys are accuracy, alt, area, bearing, building, country, datum, description, error, floor, lat, locality, lon, postalcode, region, room, speed, street, text, timestamp and uri, according to xep0080."

hooks_d['hook-post-connect'].pep =
	function ( args )
		lm.connection.bless( main.connection () ):handler ( pep_incoming_message_handler, 'message', 'normal' )
		pep_handler_registered = true
		if tune_enabled then
			mpd_callback ()
		end
		-- XXX may it confuse pairs()?
		hooks_d['hook-post-connect'].pep =
			function ( args )
				if tune_enabled then
					mpd_callback ()
				end
			end
		hooks_d['hook-quit'].pep =
			function ( args )
				if pep_handler_registered then
					lm.connection.bless( main.connection () ):handler ( pep_incoming_message_handler, 'message' )
					pep_handler_registered = false
				end
			end
	end

-- XXX this really should be initialized after connection establishment (?)
--     but as this thing is implemented by now, it will be cached by server,
--     and, thus, we will be unable to get notifications.
main.add_feature ( 'http://jabber.org/protocol/tune+notify' )
main.add_feature ( 'http://jabber.org/protocol/tune' )
main.add_feature ( 'http://jabber.org/protocol/mood+notify' )
main.add_feature ( 'http://jabber.org/protocol/mood' )
main.add_feature ( 'http://jabber.org/protocol/activity+notify' )
main.add_feature ( 'http://jabber.org/protocol/activity' )
main.add_feature ( 'http://jabber.org/protocol/geoloc+notify' )
main.add_feature ( 'http://jabber.org/protocol/geoloc' )

-- vim: se ts=4: --