|
1 |
|
2 -- PERSONAL EVENTING PROTOCOL (XEP-0163) |
|
3 |
|
4 -- library |
|
5 |
|
6 require 'lm' |
|
7 require 'iq' |
|
8 require 'mpd' |
|
9 |
|
10 -- public |
|
11 |
|
12 pep = { |
|
13 handlers = {}, |
|
14 } |
|
15 |
|
16 function pep.publish ( conn, node, data, success, fail ) |
|
17 -- local bjid = conn:jid():gsub ( '/.*', '' ) |
|
18 data.xmlns = 'http://jabber.org/protocol/' .. node |
|
19 iq.send ( conn, nil, 'set', |
|
20 { |
|
21 pubsub = { xmlns = 'http://jabber.org/protocol/pubsub', |
|
22 publish = { node = 'http://jabber.org/protocol/' .. node, |
|
23 item = { -- id = "current", |
|
24 [node] = data, |
|
25 }, |
|
26 }, |
|
27 }, |
|
28 --[[ |
|
29 configure = { |
|
30 x = { |
|
31 field = {{ type = "hidden", var = 'FORM_TYPE', |
|
32 value = { 'http://jabber.org/protocol/pubsub#node_config' }, |
|
33 },{ var = "pubsub#access_model", |
|
34 value = { 'presence' }, |
|
35 }}, |
|
36 }, |
|
37 }, |
|
38 --]] |
|
39 }, success, fail ) |
|
40 end |
|
41 |
|
42 -- private |
|
43 |
|
44 -- XXX in fact, it is not a pep handler, it is pubsub handler. |
|
45 -- should it go there? |
|
46 local pep_handler_registered = false |
|
47 local pep_incoming_message_handler = lm.message_handler.new ( |
|
48 function ( conn, mess ) |
|
49 local e = mess:child ( 'event' ) |
|
50 if e and e:attribute ( 'xmlns' ) == 'http://jabber.org/protocol/pubsub#event' then |
|
51 local is = e:child ( 'items' ) |
|
52 if is then |
|
53 local from = mess:attribute ( 'from' ) |
|
54 local node = is:attribute ( 'node' ) |
|
55 local item = is:child ( 'item' ) |
|
56 if item then |
|
57 local handled = true -- XXX should we do this? well, if it becomes general pubsub handler - then no. |
|
58 local n = item:children () |
|
59 while n do |
|
60 local xmlns = n:attribute ( 'xmlns' ) |
|
61 if pep.handlers[xmlns] then |
|
62 if not pep.handlers[xmlns] ( from, node, n ) then |
|
63 handled = false |
|
64 end |
|
65 else |
|
66 handled = false |
|
67 end |
|
68 n = n:next () |
|
69 end |
|
70 return handled |
|
71 end |
|
72 end |
|
73 end |
|
74 return false |
|
75 end ) |
|
76 |
|
77 -- mcabber |
|
78 |
|
79 local tune_enabled = false |
|
80 local mpd_pub_song = { } |
|
81 |
|
82 -- XXX in fact, we should separate library code from client code here, but it is silly. |
|
83 -- seting up four registrators, four default handlers, four handler storage variables... |
|
84 -- and, in fact, we cannot do much about complex datatypes of mood and activity. |
|
85 pep.handlers['http://jabber.org/protocol/tune'] = |
|
86 function ( from, node, data ) |
|
87 local self = false |
|
88 if from == lm.connection.bless ( main.connection () ):jid():gsub ( '/.*', '' ) then -- o_O |
|
89 self = true |
|
90 mpd_pub_song = { } |
|
91 end |
|
92 local item = data:children () |
|
93 local text = '' |
|
94 while item do |
|
95 local name = item:name () |
|
96 local value = item:value () |
|
97 if self then |
|
98 mpd_pub_song[name] = { value or '' } |
|
99 end |
|
100 text = ("%s\n- %s: %s"):format ( text, item:name (), item:value () or '' ) |
|
101 item = item:next () |
|
102 end |
|
103 if main.yesno ( main.option ( 'lua_pep_notification' ) ) then |
|
104 if text ~= '' then |
|
105 text = 'Now listening to:' .. text |
|
106 else |
|
107 text = 'Now not listening to anything' |
|
108 end |
|
109 end |
|
110 main.print_info ( from, text ) |
|
111 return true |
|
112 end |
|
113 pep.handlers['http://jabber.org/protocol/mood'] = |
|
114 function ( from, node, data ) |
|
115 if not main.yesno ( main.option ( 'lua_pep_notification' ) ) then |
|
116 return true |
|
117 end |
|
118 local item = data:children () |
|
119 local mood, desc |
|
120 while item do |
|
121 if item:name () == 'text' then |
|
122 desc = item:value () |
|
123 else |
|
124 mood = item:name () |
|
125 -- here we can add child elements handling (by namespace) |
|
126 end |
|
127 item = item:next () |
|
128 end |
|
129 if mood then |
|
130 main.print_info ( from, ("Buddy's mood now %s %s"):format ( mood, desc or '' ) ) |
|
131 else |
|
132 main.print_info ( from, "Buddy hides his mood" ) |
|
133 end |
|
134 end |
|
135 pep.handlers['http://jabber.org/protocol/activity'] = |
|
136 function ( from, node, data ) |
|
137 if not main.yesno ( main.option ( 'lua_pep_notification' ) ) then |
|
138 return true |
|
139 end |
|
140 local item = data:children () |
|
141 local activity, desc |
|
142 while item do |
|
143 if item:name () == 'text' then |
|
144 desc = item:value () |
|
145 else |
|
146 activity = item:name () |
|
147 local subitem = item:children () |
|
148 if subitem then |
|
149 -- here we can check for non-standard subactivity elements, |
|
150 -- add subactivity child elements handling |
|
151 activity = ("%s: %s"):format ( activity, subitem:name () ) |
|
152 end |
|
153 end |
|
154 item = item:next () |
|
155 end |
|
156 if activity then |
|
157 main.print_info ( from, ("Now %s %s"):format ( activity, desc or '' ) ) |
|
158 else |
|
159 main.print_info ( from, "Buddy hides his activity" ) |
|
160 end |
|
161 return true |
|
162 end |
|
163 pep.handlers['http://jabber.org/protocol/geoloc'] = |
|
164 function ( from, node, data ) |
|
165 if not main.yesno ( main.option ( 'lua_pep_notification' ) ) then |
|
166 return true |
|
167 end |
|
168 local item = data:children () |
|
169 local text = '' |
|
170 while item do |
|
171 text = ("%s\n- %s: %s"):format ( text, item:name (), item:value () or '' ) |
|
172 item = item:next () |
|
173 end |
|
174 if text ~= '' then |
|
175 text = 'Now at:' .. text |
|
176 else |
|
177 text = 'Now in unknown location' |
|
178 end |
|
179 main.print_info ( from, text ) |
|
180 return true |
|
181 end |
|
182 |
|
183 local function mpd_getstatus () |
|
184 local status = mpd.call_command { 'status' } |
|
185 if not tune_enabled or ( status.state ~= 'play' and status.state ~= 'pause' ) then |
|
186 for k, v in pairs ( mpd_pub_song ) do -- if there is anything published, publish nothing |
|
187 return { } |
|
188 end |
|
189 return nil |
|
190 end |
|
191 |
|
192 local song = mpd.call_command { 'currentsong' } |
|
193 local dir, file = song.file:match ( '(.+)/(.-)' ) |
|
194 -- populate according to currentsong fields: artist - artist, length - time, source - album, title - title, track - id, rating - ?, uri - ? |
|
195 local ret = { |
|
196 artist = { song.artist or 'Unknown' }, |
|
197 length = { song.time }, |
|
198 source = { song.album or dir }, |
|
199 title = { song.title or file }, |
|
200 track = { song.id }, |
|
201 } |
|
202 |
|
203 if not song.time or song.time == '0' then -- XXX |
|
204 ret.length = nil |
|
205 end |
|
206 |
|
207 local modified = false |
|
208 for k, v in pairs ( ret ) do |
|
209 if not mpd_pub_song[k] or mpd_pub_song[k][1] ~= v[1] then |
|
210 modified = true |
|
211 break |
|
212 end |
|
213 end |
|
214 if not modified then |
|
215 for k, v in pairs ( mpd_pub_song ) do |
|
216 if not ret[k] or ret[k][1] ~= v[1] then |
|
217 modified = true |
|
218 break |
|
219 end |
|
220 end |
|
221 end |
|
222 |
|
223 if modified then |
|
224 return ret |
|
225 else |
|
226 return nil |
|
227 end |
|
228 end |
|
229 |
|
230 local function dummy () |
|
231 end |
|
232 |
|
233 local function mpd_callback () |
|
234 local sdata = mpd_getstatus () |
|
235 if sdata then |
|
236 pep.publish ( lm.connection.bless ( main.connection () ), 'tune', sdata, dummy, dummy ) |
|
237 end |
|
238 if tune_enabled then |
|
239 return true |
|
240 else |
|
241 return false |
|
242 end |
|
243 end |
|
244 |
|
245 -- do not call it too fast, or you end up with many daemons at once |
|
246 local function enable_tune ( yn ) |
|
247 if yn == nil then |
|
248 yn = true |
|
249 end |
|
250 if yn then |
|
251 if not tune_enabled then |
|
252 main.timer ( 15, mpd_callback ) |
|
253 tune_enabled = true |
|
254 -- update status |
|
255 end |
|
256 else |
|
257 if tune_enabled then |
|
258 tune_enabled = false |
|
259 -- update status |
|
260 end |
|
261 end |
|
262 end |
|
263 |
|
264 main.command ( 'tune', |
|
265 function ( args ) |
|
266 local enable = main.yesno ( args ) |
|
267 if enable == nil then |
|
268 if tune_enabled then |
|
269 print ( "Tune notifications enabled" ) |
|
270 else |
|
271 print ( "Tune notifications disabled" ) |
|
272 end |
|
273 else |
|
274 enable_tune ( enable ) |
|
275 end |
|
276 end, false, 'yesno' ) |
|
277 main.command ( 'mood', |
|
278 function ( args ) |
|
279 local data = { } |
|
280 local mood, text = args[1], args[2] |
|
281 if text then |
|
282 data.text = { text } |
|
283 end |
|
284 if mood then |
|
285 data[mood] = { } |
|
286 end |
|
287 pep.publish ( lm.connection.bless ( main.connection () ), 'mood', data, dummy, dummy ) |
|
288 end, true ) |
|
289 main.command ( 'activity', |
|
290 function ( args ) |
|
291 local data = { } |
|
292 local activity, text = args[1], args[2] |
|
293 if text then |
|
294 data.text = { text } |
|
295 end |
|
296 local act, subact = activity:match ( "(.-)%-(.+)" ) |
|
297 if not act then |
|
298 act = activity |
|
299 end |
|
300 if act ~= '' then |
|
301 data[act] = { } |
|
302 if subact then |
|
303 data[act][subact] = { } |
|
304 end |
|
305 end |
|
306 pep.publish ( lm.connection.bless ( main.connection () ), 'activity', data, dummy, dummy ) |
|
307 end, true ) |
|
308 main.command ( 'location', |
|
309 function ( args ) |
|
310 local data = { } |
|
311 for key, val in pairs ( args ) do |
|
312 data[key] = { val } |
|
313 end |
|
314 pep.publish ( lm.connection.bless ( main.connection () ), 'geoloc', data, dummy, dummy ) |
|
315 end, true ) |
|
316 |
|
317 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)." |
|
318 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." |
|
319 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." |
|
320 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." |
|
321 |
|
322 hooks_d['hook-post-connect'].pep = |
|
323 function ( args ) |
|
324 lm.connection.bless( main.connection () ):handler ( pep_incoming_message_handler, 'message', 'normal' ) |
|
325 pep_handler_registered = true |
|
326 if tune_enabled then |
|
327 mpd_callback () |
|
328 end |
|
329 -- XXX may it confuse pairs()? |
|
330 hooks_d['hook-post-connect'].pep = |
|
331 function ( args ) |
|
332 if tune_enabled then |
|
333 mpd_callback () |
|
334 end |
|
335 end |
|
336 hooks_d['hook-quit'].pep = |
|
337 function ( args ) |
|
338 if pep_handler_registered then |
|
339 lm.connection.bless( main.connection () ):handler ( pep_incoming_message_handler, 'message' ) |
|
340 pep_handler_registered = false |
|
341 end |
|
342 end |
|
343 end |
|
344 |
|
345 -- XXX this really should be initialized after connection establishment (?) |
|
346 -- but as this thing is implemented by now, it will be cached by server, |
|
347 -- and, thus, we will be unable to get notifications. |
|
348 main.add_feature ( 'http://jabber.org/protocol/tune+notify' ) |
|
349 main.add_feature ( 'http://jabber.org/protocol/tune' ) |
|
350 main.add_feature ( 'http://jabber.org/protocol/mood+notify' ) |
|
351 main.add_feature ( 'http://jabber.org/protocol/mood' ) |
|
352 main.add_feature ( 'http://jabber.org/protocol/activity+notify' ) |
|
353 main.add_feature ( 'http://jabber.org/protocol/activity' ) |
|
354 main.add_feature ( 'http://jabber.org/protocol/geoloc+notify' ) |
|
355 main.add_feature ( 'http://jabber.org/protocol/geoloc' ) |
|
356 |
|
357 -- vim: se ts=4: -- |