One pep handler, pep commands
authorMyhailo Danylenko <isbear@ukrpost.net>
Sun, 15 Mar 2009 15:03:51 +0200
changeset 23 e441162b1386
parent 22 6460b020825d
child 24 25552b21d3fb
One pep handler, pep commands
examples/mcabberrc.lua
examples/mpd.lua
examples/xep0163.lua
--- a/examples/mcabberrc.lua	Sun Mar 15 02:40:32 2009 +0200
+++ b/examples/mcabberrc.lua	Sun Mar 15 15:03:51 2009 +0200
@@ -334,10 +334,6 @@
 
 dopath 'marking'
 
--- MPD (in fact, XEP-0118)
-
-dopath 'mpd'
-
 -- FORMS (XEP-0004)
 
 dopath 'xep0004'
@@ -350,7 +346,7 @@
 
 dopath 'xep0047'
 
--- PUBSUB (XEP-0060)
+-- PUBLISH-SUBSRIBE (XEP-0060)
 
 dopath 'xep0060'
 
@@ -362,6 +358,10 @@
 
 dopath 'xep0146'
 
+-- PERSONAL EVENTING PROTOCOL (XEP-0163)
+
+dopath 'xep0163'
+
 -- JOBS
 
 dopath 'jobs'
--- a/examples/mpd.lua	Sun Mar 15 02:40:32 2009 +0200
+++ b/examples/mpd.lua	Sun Mar 15 15:03:51 2009 +0200
@@ -2,7 +2,7 @@
 -- Requires libsocket
 
 -- TODO:
--- do pubsub tunes+notify instead of status hacking
+-- password support
 
 require 'socket'
 
@@ -21,7 +21,7 @@
 function mpd.receive_message ( tcp, separator )
 	local ret  = {}
 	local line = tcp:receive ( '*l' )
-	while line and not ( line:find ( '^OK' ) or line:find ( '^ACK' ) ) do
+	while line and not ( line:match ( '^OK' ) or line:match ( '^ACK' ) ) do
 		local key, val = line:match ( '^(.-):%s*(.*)$' )
 		if key then
 			if separator then
@@ -99,258 +99,4 @@
 	end
 end
 
--- MCABBER PART --
-
-mpd_enabled = false
-
--- XXX: to C?
-char2status = {
-	f = 'free',
-	o = 'online',
-	a = 'away',
-	d = 'dnd',
-	n = 'notavail',
-	i = 'invisible',
-	['_'] = 'offline',
-	['?'] = 'message',
-}
-
-mpd_pub_song = { xmlns = 'http://jabber.org/protocol/tune' }
-
-function mpd_getstatus ()
-	local status   = mpd.call_command { 'status' }
-	if not mpd_enabled or ( status.state ~= 'play' and status.state ~= 'pause' ) then
-		if mpd_pub_song.artist or mpd_pub_song.length or mpd_pub_song.source or mpd_pub_song.title or mpd_pub_song.track then
-			mpd_pub_song.artist = nil
-			mpd_pub_song.length = nil
-			mpd_pub_song.source = nil
-			mpd_pub_song.title  = nil
-			mpd_pub_song.track  = nil
-			return mpd_pub_song
-		else
-			return nil
-		end
-	end
-	
-	local modified  = false
-	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 artist, length, source, title, track = song.artist, song.time, song.album, song.title, song.id
-
-	if not artist or artist == '' then
-		artist = 'Unknown'
-	end
-	if not mpd_pub_song.artist or artist ~= mpd_pub_song.artist[1] then
-		mpd_pub_song.artist = { artist }
-		modified = true
-	end
-
-	if length and length ~= 0 then
-		if not mpd_pub_song.length or length ~= mpd_pub_song.length[1] then
-			mpd_pub_song.length = { length }
-			modified = true
-		end
-	elseif mpd_pub_song.length then -- no length
-		mpd_pub_song.length = nil
-		modified = true
-	end
-
-	if not source or source == '' then
-		source = dir
-	end
-	if not mpd_pub_song.source or source ~= mpd_pub_song.source[1] then
-		mpd_pub_song.source = { source }
-		modified = true
-	end
-
-	if not title or title == '' then
-		title = file
-	end
-	if not mpd_pub_song.title or title ~= mpd_pub_song.title[1] then
-		mpd_pub_song.title = { title }
-		modified = true
-	end
-
-	if not mpd_pub_song.track or track ~= mpd_pub_song.track[1] then
-		mpd_pub_song.track = { track }
-		modified = true
-	end
-
-	if modified then
-		return mpd_pub_song
-	else
-		return nil
-	end
-end
-
-function parse_status ()
-	local stletter, stmessage = main.status ()
-	local cmd = char2status[stletter]
-	local message, mpd_string = stmessage:match ( "^(.-)%s+(%[mpd:%s+.+%s*%])" )
-	if message then
-		return cmd, message, mpd_string
-	else
-		return cmd, stmessage, ''
-	end
-end
-
-function mpd_callback ()
-	local sdata = mpd_getstatus ()
-	if sdata then
-		local conn = lm.connection.bless ( main.connection () )
-		if conn:status () == 'authenticated' then
---			local bjid = conn:jid():gsub ( '/.*', '' )
-			conn:send (
-				lm.message.create { mtype = 'iq-set', --from = conn:jid (), to = bjid,
-					pubsub = { xmlns = 'http://jabber.org/protocol/pubsub',
-						publish = { node = 'http://jabber.org/protocol/tune',
-							item = { --id = "current",
-								tune = sdata,
-							},
-						},
-					},
---[[					configure = {
-						x = {
-							field = {{ type = "hidden", var = 'FORM_TYPE',
-								value = { 'http://jabber.org/protocol/pubsub#node_config' },
-							},{ var = "pubsub#access_model",
-								value = { 'presence' },
-							}},
-						},
-					},--]]
-				})
-		end
-	end
-	if mpd_enabled then
-		return true
-	else
-		return false
-	end
-end
-
--- do not call it too fast, or you end up with many daemons at once
-function enable_mpd ( yn )
-	if yn == nil then
-		yn = true
-	end
-	if yn then
-		if not mpd_enabled then
-			main.timer ( 15, mpd_callback )
-			mpd_enabled = true
-			-- update status
-		end
-	else
-		if mpd_enabled then
-			mpd_enabled = false
-			-- update status
-		end
-	end
-end
-
-main.command ( 'mpd',
-	function ( args )
-		local enable = yesno ( args )
-		if enable == nil then
-			if mpd_enabled then
-				print ( "MPD status string is enabled" )
-			else
-				print ( "MPD status string is disabled" )
-			end
-		else
-			enable_mpd ( enable )
-		end
-	end, boolean_cid )
-
-commands_help['mpd'] = "[enable|disable|on|off|yes|no|true|false]\n\nSets state of mpd string in your status.\nIf state is omitted, prints current state."
-
-mpd_incoming_message_handler = lm.message_handler.new (
-	function ( conn, mess )
-		-- we can add that validation stuff later, if it will be necessary
-		local tune = mess:path ( 'event', 'items', 'item', 'tune' )
-		if tune then
-			local from = mess:attribute ( 'from' )
-			local item = tune:children ()
-			main.print_info ( from, "Tunes notification:" )
-			while item do
-				main.print_info ( from, ("- %s: %s"):format ( item:name (), item:value () or '' ) )
-				item = item:next ()
-			end
-			return true
-		end
-		return false
-	end )
-
-mood_incoming_message_handler = lm.message_handler.new (
-	function ( conn, mess )
-		-- we can add that validation stuff later, if it will be necessary
-		local tune = mess:path ( 'event', 'items', 'item', 'mood' )
-		if tune then
-			local from = mess:attribute ( 'from' )
-			local item = tune:children ()
-			main.print_info ( from, "Mood notification:" )
-			while item do
-				main.print_info ( from, ("- %s: %s"):format ( item:name (), item:value () or '' ) )
-				item = item:next ()
-			end
-			return true
-		end
-		return false
-	end )
-
-activity_incoming_message_handler = lm.message_handler.new (
-	function ( conn, mess )
-		-- we can add that validation stuff later, if it will be necessary
-		local tune = mess:path ( 'event', 'items', 'item', 'activity' )
-		if tune then
-			local from = mess:attribute ( 'from' )
-			local item = tune:children ()
-			main.print_info ( from, "Activity notification:" )
-			while item do
-				main.print_info ( from, ("- %s: %s"):format ( item:name (), item:value () or '' ) )
-				item = item:next ()
-			end
-			return true
-		end
-		return false
-	end )
-
-mpd_handler_registered = false
-
-hooks_d['hook-post-connect'].mpd =
-	function ( args )
-		lm.connection.bless( main.connection () ):handler ( mpd_incoming_message_handler, 'message', 'normal' )
-		lm.connection.bless( main.connection () ):handler ( mood_incoming_message_handler, 'message', 'normal' )
-		lm.connection.bless( main.connection () ):handler ( activity_incoming_message_handler, 'message', 'normal' )
-		mpd_handler_registered = true
-		if mpd_enabled then
-			mpd_callback ()
-		end
-		hooks_d['hook-post-connect'].mpd =
-			function ( args )
-				if mpd_enabled then
-					mpd_callback ()
-				end
-			end
-		hooks_d['hook-quit'].mpd =
-			function ( args )
-				if mpd_handler_registered then
-					lm.connection.bless( main.connection () ):handler ( mpd_incoming_message_handler, 'message' )
-					lm.connection.bless( main.connection () ):handler ( mood_incoming_message_handler, 'message' )
-					lm.connection.bless( main.connection () ):handler ( activity_incoming_message_handler, 'message' )
-					mpd_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' )
-
 -- vim: se ts=4: --
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/xep0163.lua	Sun Mar 15 15:03:51 2009 +0200
@@ -0,0 +1,358 @@
+
+dopath 'mpd'
+
+tune_enabled = false
+
+mpd_pub_song = { }
+
+function mpd_getstatus ()
+	local status   = mpd.call_command { 'status' }
+	if not tune_enabled or ( status.state ~= 'play' and status.state ~= 'pause' ) then
+		if mpd_pub_song.artist or mpd_pub_song.length or mpd_pub_song.source or mpd_pub_song.title or mpd_pub_song.track then
+			mpd_pub_song.artist = nil
+			mpd_pub_song.length = nil
+			mpd_pub_song.source = nil
+			mpd_pub_song.title  = nil
+			mpd_pub_song.track  = nil
+			return mpd_pub_song
+		else
+			return nil
+		end
+	end
+	
+	local modified  = false
+	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 artist, length, source, title, track = song.artist, song.time, song.album, song.title, song.id
+
+	if not artist or artist == '' then
+		artist = 'Unknown'
+	end
+	if not mpd_pub_song.artist or artist ~= mpd_pub_song.artist[1] then
+		mpd_pub_song.artist = { artist }
+		modified = true
+	end
+
+	if length and length ~= 0 then
+		if not mpd_pub_song.length or length ~= mpd_pub_song.length[1] then
+			mpd_pub_song.length = { length }
+			modified = true
+		end
+	elseif mpd_pub_song.length then -- no length
+		mpd_pub_song.length = nil
+		modified = true
+	end
+
+	if not source or source == '' then
+		source = dir
+	end
+	if not mpd_pub_song.source or source ~= mpd_pub_song.source[1] then
+		mpd_pub_song.source = { source }
+		modified = true
+	end
+
+	if not title or title == '' then
+		title = file
+	end
+	if not mpd_pub_song.title or title ~= mpd_pub_song.title[1] then
+		mpd_pub_song.title = { title }
+		modified = true
+	end
+
+	if not mpd_pub_song.track or track ~= mpd_pub_song.track[1] then
+		mpd_pub_song.track = { track }
+		modified = true
+	end
+
+	if modified then
+		return mpd_pub_song
+	else
+		return nil
+	end
+end
+
+function pep_publish ( node, data )
+	local conn = lm.connection.bless ( main.connection () )
+	data.xmlns = 'http://jabber.org/protocol/' .. node -- this may modify original data? imo it does not matter.
+	if conn:status () == 'authenticated' then
+--		local bjid = conn:jid():gsub ( '/.*', '' )
+		conn:send (
+			lm.message.create { mtype = 'iq-set', -- from = conn:jid (), to = bjid,
+				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' },
+						}},
+					},
+				},
+--]]
+			},
+			function ( conn, mess )
+				local mtype, smtype = mess:type ()
+				if smtype == 'result' then
+					return true
+				elseif smtype == 'error' then
+					print ( 'Error publishing to ' .. node .. ': ' .. mess:xml () ) -- FIXME
+					return true
+				else
+					print ( 'Weird ansver to publishing request: ' .. mess:xml () )
+					return false
+				end
+			end )
+	end
+end
+
+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 enable = yesno ( main.option ( 'pep_notification' ) )
+			if enable == false then
+				return true
+			end
+			local is = e:child ( 'items' )
+			if is then
+				local from = mess:attribute ( 'from' )
+				local node = is:attribute ( 'node' )
+				if node == 'http://jabber.org/protocol/tune' then
+					local tune = is:path ( 'item', 'tune' )
+					if tune then
+						local item = tune: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 listening to:' .. text
+						else
+							text = 'Now not listening to anything'
+						end
+						main.print_info ( from, text )
+						return true
+					else
+						main.print_info ( from, 'Strange: no tune item in pep notification' )
+					end
+				elseif node == 'http://jabber.org/protocol/mood' then
+					local mood = is:path ( 'item', 'mood' )
+					if mood then
+						local item = mood: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
+						return true
+					else
+						main.print_info ( from, 'Strange: no mood item in pep notification' )
+					end
+				elseif node == 'http://jabber.org/protocol/activity' then
+					local activity = is:path ( 'item', 'activity' )
+					if activity then
+						local item = activity: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
+					else
+						main.print_info ( from, 'Strange: no activity item in pep notification' )
+					end
+				elseif node == 'http://jabber.org/protocol/geoloc' then
+					local loc = is:path ( 'item', 'geoloc' )
+					if loc then
+						local item = loc: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
+					else
+						main.print_info ( from, 'Strange: no geoloc item in pep notification' )
+					end
+				else
+					main.print_info ( from, 'Unknown node in pep notification: ' .. is:xml () )
+				end
+			end
+		end
+		return false
+	end )
+
+
+function mpd_callback ()
+	local sdata = mpd_getstatus ()
+	if sdata then
+		pep_publish ( 'tune', sdata )
+	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
+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 = 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, boolean_cid )
+main.command ( 'mood',
+	function ( args )
+		local data = { }
+		local mood, text = args:match ( "(%S-)%s+(.+)" )
+		if mood then
+			data.text  = { text }
+		else
+			mood = args
+		end
+		if mood ~= '' then
+			data[mood] = { }
+		end
+		pep_publish ( 'mood', data )
+	end )
+main.command ( 'activity',
+	function ( args )
+		local data = { }
+		local activity, text = args:match ( "(%S-)%s+(.+)" )
+		if activity then
+			data.text = { text }
+		else
+			activity = args
+		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 ( 'activity', data )
+	end )
+main.command ( 'location',
+	function ( args )
+		local data = { }
+		local key, val, remains = args:match ( '%s*(%S+)%s+(%S+)(.*)' )
+		while key do
+			data[key] = { val }
+			key, val, remains = args:match ( '%s*(%S+)%s+(%S+)(.*)' )
+		end
+		pep_publish ( 'geoloc', data )
+	end )
+
+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."
+
+pep_handler_registered = false
+
+hooks_d['hook-post-connect'].xep0163 =
+	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'].xep0163 =
+			function ( args )
+				if tune_enabled then
+					mpd_callback ()
+				end
+			end
+		hooks_d['hook-quit'].xep0163 =
+			function ( args )
+				if mpd_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: --