corrade-lsl-templates – Diff between revs 41 and 42

Subversion Repositories:
Rev:
Only display areas with differencesIgnore whitespace
Rev 41 Rev 42
1 /////////////////////////////////////////////////////////////////////////// 1 ///////////////////////////////////////////////////////////////////////////
2 // Copyright (C) Wizardry and Steamworks 2016 - License: CC BY 2.0 // 2 // Copyright (C) Wizardry and Steamworks 2016 - License: CC BY 2.0 //
3 /////////////////////////////////////////////////////////////////////////// 3 ///////////////////////////////////////////////////////////////////////////
4 // 4 //
5 // A module that sends the current Corrade heartbeat to group chat. 5 // A module that sends the current Corrade heartbeat to group chat.
6 // 6 //
7 /////////////////////////////////////////////////////////////////////////// 7 ///////////////////////////////////////////////////////////////////////////
8   8  
9 /////////////////////////////////////////////////////////////////////////// 9 ///////////////////////////////////////////////////////////////////////////
10 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // 10 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
11 /////////////////////////////////////////////////////////////////////////// 11 ///////////////////////////////////////////////////////////////////////////
12 string wasKeyValueGet(string k, string data) { 12 string wasKeyValueGet(string k, string data) {
13 if(llStringLength(data) == 0) return ""; 13 if(llStringLength(data) == 0) return "";
14 if(llStringLength(k) == 0) return ""; 14 if(llStringLength(k) == 0) return "";
15 list a = llParseStringKeepNulls(data, ["&", "="], []); 15 list a = llParseStringKeepNulls(data, ["&", "="], []);
16 integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]); 16 integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
17 if(i != -1) return llList2String(a, 2*i+1); 17 if(i != -1) return llList2String(a, 2*i+1);
18 return ""; 18 return "";
19 } 19 }
20 20  
21 /////////////////////////////////////////////////////////////////////////// 21 ///////////////////////////////////////////////////////////////////////////
22 // Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0 // 22 // Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0 //
23 /////////////////////////////////////////////////////////////////////////// 23 ///////////////////////////////////////////////////////////////////////////
24 string wasKeyValueEncode(list data) { 24 string wasKeyValueEncode(list data) {
25 list k = llList2ListStrided(data, 0, -1, 2); 25 list k = llList2ListStrided(data, 0, -1, 2);
26 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2); 26 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
27 data = []; 27 data = [];
28 do { 28 do {
29 data += llList2String(k, 0) + "=" + llList2String(v, 0); 29 data += llList2String(k, 0) + "=" + llList2String(v, 0);
30 k = llDeleteSubList(k, 0, 0); 30 k = llDeleteSubList(k, 0, 0);
31 v = llDeleteSubList(v, 0, 0); 31 v = llDeleteSubList(v, 0, 0);
32 } while(llGetListLength(k) != 0); 32 } while(llGetListLength(k) != 0);
33 return llDumpList2String(data, "&"); 33 return llDumpList2String(data, "&");
34 } 34 }
35   35  
36 /////////////////////////////////////////////////////////////////////////// 36 ///////////////////////////////////////////////////////////////////////////
37 // Copyright (C) 2011 Wizardry and Steamworks - License: CC BY 2.0 // 37 // Copyright (C) 2011 Wizardry and Steamworks - License: CC BY 2.0 //
38 /////////////////////////////////////////////////////////////////////////// 38 ///////////////////////////////////////////////////////////////////////////
39 // http://was.fm/secondlife/wanderer 39 // http://was.fm/secondlife/wanderer
40 vector wasCirclePoint(float radius) { 40 vector wasCirclePoint(float radius) {
41 float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); 41 float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
42 float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2); 42 float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
43 if(llPow(x,2) + llPow(y,2) <= llPow(radius,2)) 43 if(llPow(x,2) + llPow(y,2) <= llPow(radius,2))
44 return <x, y, 0>; 44 return <x, y, 0>;
45 return wasCirclePoint(radius); 45 return wasCirclePoint(radius);
46 } 46 }
47   47  
48 /////////////////////////////////////////////////////////////////////////// 48 ///////////////////////////////////////////////////////////////////////////
49 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // 49 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
50 /////////////////////////////////////////////////////////////////////////// 50 ///////////////////////////////////////////////////////////////////////////
51 // escapes a string in conformance with RFC1738 51 // escapes a string in conformance with RFC1738
52 string wasURLEscape(string i) { 52 string wasURLEscape(string i) {
53 string o = ""; 53 string o = "";
54 do { 54 do {
55 string c = llGetSubString(i, 0, 0); 55 string c = llGetSubString(i, 0, 0);
56 i = llDeleteSubString(i, 0, 0); 56 i = llDeleteSubString(i, 0, 0);
57 if(c == "") jump continue; 57 if(c == "") jump continue;
58 if(c == " ") { 58 if(c == " ") {
59 o += "+"; 59 o += "+";
60 jump continue; 60 jump continue;
61 } 61 }
62 if(c == "\n") { 62 if(c == "\n") {
63 o += "%0D" + llEscapeURL(c); 63 o += "%0D" + llEscapeURL(c);
64 jump continue; 64 jump continue;
65 } 65 }
66 o += llEscapeURL(c); 66 o += llEscapeURL(c);
67 @continue; 67 @continue;
68 } while(i != ""); 68 } while(i != "");
69 return o; 69 return o;
70 } 70 }
71   71  
72 /////////////////////////////////////////////////////////////////////////// 72 ///////////////////////////////////////////////////////////////////////////
73 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // 73 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
74 /////////////////////////////////////////////////////////////////////////// 74 ///////////////////////////////////////////////////////////////////////////
75 list wasCSVToList(string csv) { 75 list wasCSVToList(string csv) {
76 list l = []; 76 list l = [];
77 list s = []; 77 list s = [];
78 string m = ""; 78 string m = "";
79 do { 79 do {
80 string a = llGetSubString(csv, 0, 0); 80 string a = llGetSubString(csv, 0, 0);
81 csv = llDeleteSubString(csv, 0, 0); 81 csv = llDeleteSubString(csv, 0, 0);
82 if(a == ",") { 82 if(a == ",") {
83 if(llList2String(s, -1) != "\"") { 83 if(llList2String(s, -1) != "\"") {
84 l += m; 84 l += m;
85 m = ""; 85 m = "";
86 jump continue; 86 jump continue;
87 } 87 }
88 m += a; 88 m += a;
89 jump continue; 89 jump continue;
90 } 90 }
91 if(a == "\"" && llGetSubString(csv, 0, 0) == a) { 91 if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
92 m += a; 92 m += a;
93 csv = llDeleteSubString(csv, 0, 0); 93 csv = llDeleteSubString(csv, 0, 0);
94 jump continue; 94 jump continue;
95 } 95 }
96 if(a == "\"") { 96 if(a == "\"") {
97 if(llList2String(s, -1) != a) { 97 if(llList2String(s, -1) != a) {
98 s += a; 98 s += a;
99 jump continue; 99 jump continue;
100 } 100 }
101 s = llDeleteSubList(s, -1, -1); 101 s = llDeleteSubList(s, -1, -1);
102 jump continue; 102 jump continue;
103 } 103 }
104 m += a; 104 m += a;
105 @continue; 105 @continue;
106 } while(csv != ""); 106 } while(csv != "");
107 // postcondition: length(s) = 0 107 // postcondition: length(s) = 0
108 return l + m; 108 return l + m;
109 } 109 }
110   110  
111 /////////////////////////////////////////////////////////////////////////// 111 ///////////////////////////////////////////////////////////////////////////
112 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // 112 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
113 /////////////////////////////////////////////////////////////////////////// 113 ///////////////////////////////////////////////////////////////////////////
114 string wasListToCSV(list l) { 114 string wasListToCSV(list l) {
115 list v = []; 115 list v = [];
116 do { 116 do {
117 string a = llDumpList2String( 117 string a = llDumpList2String(
118 llParseStringKeepNulls( 118 llParseStringKeepNulls(
119 llList2String( 119 llList2String(
120 l, 120 l,
121 0 121 0
122 ), 122 ),
123 ["\""], 123 ["\""],
124 [] 124 []
125 ), 125 ),
126 "\"\"" 126 "\"\""
127 ); 127 );
128 if(llParseStringKeepNulls( 128 if(llParseStringKeepNulls(
129 a, 129 a,
130 [" ", ",", "\n", "\""], [] 130 [" ", ",", "\n", "\""], []
131 ) != 131 ) !=
132 (list) a 132 (list) a
133 ) a = "\"" + a + "\""; 133 ) a = "\"" + a + "\"";
134 v += a; 134 v += a;
135 l = llDeleteSubList(l, 0, 0); 135 l = llDeleteSubList(l, 0, 0);
136 } while(l != []); 136 } while(l != []);
137 return llDumpList2String(v, ","); 137 return llDumpList2String(v, ",");
138 } 138 }
139   139  
140 /////////////////////////////////////////////////////////////////////////// 140 ///////////////////////////////////////////////////////////////////////////
141 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 // 141 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
142 /////////////////////////////////////////////////////////////////////////// 142 ///////////////////////////////////////////////////////////////////////////
143 // unescapes a string in conformance with RFC1738 143 // unescapes a string in conformance with RFC1738
144 string wasURLUnescape(string i) { 144 string wasURLUnescape(string i) {
145 return llUnescapeURL( 145 return llUnescapeURL(
146 llDumpList2String( 146 llDumpList2String(
147 llParseString2List( 147 llParseString2List(
148 llDumpList2String( 148 llDumpList2String(
149 llParseString2List( 149 llParseString2List(
150 i, 150 i,
151 ["+"], 151 ["+"],
152 [] 152 []
153 ), 153 ),
154 " " 154 " "
155 ), 155 ),
156 ["%0D%0A"], 156 ["%0D%0A"],
157 [] 157 []
158 ), 158 ),
159 "\n" 159 "\n"
160 ) 160 )
161 ); 161 );
162 } 162 }
163   163  
164 /////////////////////////////////////////////////////////////////////////// 164 ///////////////////////////////////////////////////////////////////////////
165 // Copyright (C) 2017 Wizardry and Steamworks - License: CC BY 2.0 // 165 // Copyright (C) 2017 Wizardry and Steamworks - License: CC BY 2.0 //
166 /////////////////////////////////////////////////////////////////////////// 166 ///////////////////////////////////////////////////////////////////////////
167 list wasSetIntersect(list a, list b) { 167 list wasSetIntersect(list a, list b) {
168 if(llGetListLength(a) == 0) return []; 168 if(llGetListLength(a) == 0) return [];
169 string i = llList2String(a, 0); 169 string i = llList2String(a, 0);
170 a = llDeleteSubList(a, 0, 0); 170 a = llDeleteSubList(a, 0, 0);
171 if(llListFindList(b, (list)i) == -1) 171 if(llListFindList(b, (list)i) == -1)
172 return wasSetIntersect(a, b); 172 return wasSetIntersect(a, b);
173 return i + wasSetIntersect(a, b); 173 return i + wasSetIntersect(a, b);
174 } 174 }
175   175  
176 // configuration data 176 // configuration data
177 string configuration = ""; 177 string configuration = "";
178 // callback URL 178 // callback URL
179 string URL = ""; 179 string URL = "";
180 // store message over state. 180 // store message over state.
181 string data = ""; 181 string data = "";
182   182  
183 default { 183 default {
184 state_entry() { 184 state_entry() {
185 llOwnerSay("[Heartbeat] Starting..."); 185 llOwnerSay("[Heartbeat] Starting...");
186 llSetTimerEvent(10); 186 llSetTimerEvent(10);
187 } 187 }
188 link_message(integer sender, integer num, string message, key id) { 188 link_message(integer sender, integer num, string message, key id) {
189 if(id != "configuration") return; 189 if(id != "configuration") return;
190 llOwnerSay("[Heartbeat] Got configuration..."); 190 llOwnerSay("[Heartbeat] Got configuration...");
191 configuration = message; 191 configuration = message;
192 state listen_group; 192 state listen_group;
193 } 193 }
194 timer() { 194 timer() {
195 llOwnerSay("[Heartbeat] Requesting configuration..."); 195 llOwnerSay("[Heartbeat] Requesting configuration...");
196 llMessageLinked(LINK_THIS, 0, "configuration", NULL_KEY); 196 llMessageLinked(LINK_THIS, 0, "configuration", NULL_KEY);
197 } 197 }
198 on_rez(integer num) { 198 on_rez(integer num) {
199 llResetScript(); 199 llResetScript();
200 } 200 }
201 changed(integer change) { 201 changed(integer change) {
202 if((change & CHANGED_INVENTORY) || 202 if((change & CHANGED_INVENTORY) ||
203 (change & CHANGED_REGION_START) || 203 (change & CHANGED_REGION_START) ||
204 (change & CHANGED_OWNER)) { 204 (change & CHANGED_OWNER)) {
205 llResetScript(); 205 llResetScript();
206 } 206 }
207 } 207 }
208 state_exit() { 208 state_exit() {
209 llSetTimerEvent(0); 209 llSetTimerEvent(0);
210 } 210 }
211 } 211 }
212   212  
213 state listen_group { 213 state listen_group {
214 state_entry() { 214 state_entry() {
215 // DEBUG 215 // DEBUG
216 llOwnerSay("[Heartbeat] Waiting for group messages..."); 216 llOwnerSay("[Heartbeat] Waiting for group messages...");
217 } 217 }
218 link_message(integer sender, integer num, string message, key id) { 218 link_message(integer sender, integer num, string message, key id) {
219 // We only care about notifications now. 219 // We only care about notifications now.
220 if(id != "notification") 220 if(id != "notification")
221 return; 221 return;
222 222  
223 // This script only processes group notifications. 223 // This script only processes group notifications.
224 if(wasKeyValueGet("type", message) != "group" || 224 if(wasKeyValueGet("type", message) != "group" ||
225 (wasKeyValueGet("type", message) == "group" && 225 (wasKeyValueGet("type", message) == "group" &&
226 wasURLUnescape(wasKeyValueGet("group", message)) != 226 wasURLUnescape(wasKeyValueGet("group", message)) !=
227 wasKeyValueGet("group", configuration))) 227 wasKeyValueGet("group", configuration)))
228 return; 228 return;
229 229  
230 // Get the sent message. 230 // Get the sent message.
231 data = wasURLUnescape( 231 data = wasURLUnescape(
232 wasKeyValueGet( 232 wasKeyValueGet(
233 "message", 233 "message",
234 message 234 message
235 ) 235 )
236 ); 236 );
237 237  
238 // Check if this is an eggdrop command. 238 // Check if this is an eggdrop command.
239 if(llGetSubString(data, 0, 0) != 239 if(llGetSubString(data, 0, 0) !=
240 wasKeyValueGet("command", configuration)) 240 wasKeyValueGet("command", configuration))
241 return; 241 return;
242 242  
243 // Check if the command matches the current module. 243 // Check if the command matches the current module.
244 list command = llParseString2List(data, [" "], []); 244 list command = llParseString2List(data, [" "], []);
245 if(llList2String(command, 0) != 245 if(llList2String(command, 0) !=
246 wasKeyValueGet("command", configuration) + "heartbeat") 246 wasKeyValueGet("command", configuration) + "heartbeat")
247 return; 247 return;
248 248  
249 // Remove command. 249 // Remove command.
250 command = llDeleteSubList(command, 0, 0); 250 command = llDeleteSubList(command, 0, 0);
251   251  
252 // Get an URL. -  
253 state url; -  
254 } -  
255 on_rez(integer num) { -  
256 llResetScript(); -  
257 } -  
258 changed(integer change) { -  
259 if((change & CHANGED_INVENTORY) || -  
260 (change & CHANGED_REGION_START) || -  
261 (change & CHANGED_OWNER)) { -  
262 llResetScript(); -  
263 } -  
264 } -  
265 } -  
266   -  
267 state url { -  
268 state_entry() { -  
269 // DEBUG -  
270 llOwnerSay("[Heartbeat] Requesting URL..."); -  
271 llRequestURL(); -  
272 } -  
273 http_request(key id, string method, string body) { -  
274 if(method != URL_REQUEST_GRANTED) return; -  
275 URL = body; -  
276 // DEBUG -  
277 llOwnerSay("[Heartbeat] Got URL..."); 252 // Get heartbeat.
278 state version; 253 state heartbeat;
279 } 254 }
280 on_rez(integer num) { 255 on_rez(integer num) {
281 llResetScript(); 256 llResetScript();
282 } 257 }
283 changed(integer change) { 258 changed(integer change) {
284 if((change & CHANGED_INVENTORY) || 259 if((change & CHANGED_INVENTORY) ||
285 (change & CHANGED_REGION_START) || 260 (change & CHANGED_REGION_START) ||
286 (change & CHANGED_OWNER)) { 261 (change & CHANGED_OWNER)) {
287 llResetScript(); 262 llResetScript();
288 } 263 }
289 } 264 }
290 } 265 }
291   266  
292 state version { 267 state heartbeat {
293 state_entry() { 268 state_entry() {
294 // DEBUG 269 // DEBUG
295 llOwnerSay("[Heartbeat] Getting heartbeat data..."); 270 llOwnerSay("[Heartbeat] Getting heartbeat data...");
296 llInstantMessage( 271 llInstantMessage(
297 wasKeyValueGet( 272 wasKeyValueGet(
298 "corrade", 273 "corrade",
299 configuration 274 configuration
300 ), 275 ),
301 wasKeyValueEncode( 276 wasKeyValueEncode(
302 [ 277 [
303 "command", "getheartbeatdata", 278 "command", "getheartbeatdata",
304 "group", wasURLEscape( 279 "group", wasURLEscape(
305 wasKeyValueGet( 280 wasKeyValueGet(
306 "group", 281 "group",
307 configuration 282 configuration
308 ) 283 )
309 ), 284 ),
310 "password", wasURLEscape( 285 "password", wasURLEscape(
311 wasKeyValueGet( 286 wasKeyValueGet(
312 "password", 287 "password",
313 configuration 288 configuration
314 ) 289 )
315 ), 290 ),
316 "data", wasListToCSV( 291 "data", wasListToCSV(
317 [ 292 [
318 "AverageCPUUsage", 293 "AverageCPUUsage",
319 "AverageRAMUsage", 294 "AverageRAMUsage",
-   295 "AverageNETUsage",
320 "Uptime" 296 "Uptime"
321 ] 297 ]
322 ), 298 ),
323 "callback", wasURLEscape(URL) 299 "callback", wasURLEscape(
-   300 wasKeyValueGet(
-   301 "URL",
-   302 configuration
-   303 )
-   304 )
324 ] 305 ]
325 ) 306 )
326 ); 307 );
327 llSetTimerEvent(60); 308 llSetTimerEvent(60);
328 } 309 }
329 http_request(key id, string method, string body) { 310 link_message(integer sender, integer num, string body, key id) {
330 llHTTPResponse(id, 200, "OK"); 311 // Only process callbacks for the database command.
331 llReleaseURL(URL); -  
332 if(wasKeyValueGet("command", body) != "getheartbeatdata" || 312 if(id != "callback" || wasKeyValueGet("command", body) != "getheartbeatdata")
-   313 return;
-   314  
333 wasKeyValueGet("success", body) != "True") { 315 if(wasKeyValueGet("success", body) != "True") {
334 // DEBUG 316 // DEBUG
335 llOwnerSay("[Heartbeat] Unable to get heartbeat data: " + 317 llOwnerSay("[Heartbeat] Unable to get heartbeat data: " +
336 wasURLUnescape( 318 wasURLUnescape(
337 wasKeyValueGet("error", body) 319 wasKeyValueGet("error", body)
338 ) 320 )
339 ); 321 );
340 state listen_group; 322 state listen_group;
341 } 323 }
342 324  
343 list stats = wasCSVToList( 325 list stats = wasCSVToList(
344 wasURLUnescape( 326 wasURLUnescape(
345 wasKeyValueGet( 327 wasKeyValueGet(
346 "data", 328 "data",
347 body 329 body
348 ) 330 )
349 ) 331 )
350 ); 332 );
351 333  
352 string CPU = llList2String( 334 string CPU = llList2String(
353 stats, 335 stats,
354 llListFindList( 336 llListFindList(
355 stats, 337 stats,
356 ["AverageCPUUsage"] 338 ["AverageCPUUsage"]
357 ) + 1 339 ) + 1
358 ); 340 );
359 341  
360 // AverageRAMUsage is returned in bytes, 342 // AverageRAMUsage is returned in bytes,
361 // so convert the value to MiB and round. 343 // so convert the value to MiB and round.
362 string RAM = (string)llRound( 344 string RAM = (string)llRound(
363 llList2Float( 345 llList2Float(
364 stats, 346 stats,
365 llListFindList( 347 llListFindList(
366 stats, 348 stats,
367 ["AverageRAMUsage"] 349 ["AverageRAMUsage"]
368 ) + 1 350 ) + 1
369 ) / 1024 / 1024 351 ) / 1024 / 1024
370 ); 352 );
371 353  
-   354 // AverageNETUsage is returned in bytes,
-   355 // so convert the value to MiB and round.
-   356 string NET = (string)llRound(
-   357 llList2Float(
-   358 stats,
-   359 llListFindList(
-   360 stats,
-   361 ["AverageNETUsage"]
-   362 ) + 1
-   363 ) / 1024 / 1024
-   364 );
-   365  
372 string uptime = llList2String( 366 string uptime = llList2String(
373 stats, 367 stats,
374 llListFindList( 368 llListFindList(
375 stats, 369 stats,
376 ["Uptime"] 370 ["Uptime"]
377 ) + 1 371 ) + 1
378 ); 372 );
379 373  
380 data = "RAM: " + RAM + "MiB" + " CPU: " + CPU + "%" + " Uptime: " + uptime; 374 data = "RAM: " + RAM + "MiB" + " CPU: " + CPU + "%" + " NET: " + NET + "MiB" + " Uptime: " + uptime;
381 375  
382 state tell; 376 state tell;
383 } 377 }
384 timer() { 378 timer() {
385 llReleaseURL(URL); -  
386 state listen_group; 379 state listen_group;
387 } 380 }
388 on_rez(integer num) { 381 on_rez(integer num) {
389 llResetScript(); 382 llResetScript();
390 } 383 }
391 changed(integer change) { 384 changed(integer change) {
392 if((change & CHANGED_INVENTORY) || 385 if((change & CHANGED_INVENTORY) ||
393 (change & CHANGED_REGION_START) || 386 (change & CHANGED_REGION_START) ||
394 (change & CHANGED_OWNER)) { 387 (change & CHANGED_OWNER)) {
395 llResetScript(); 388 llResetScript();
396 } 389 }
397 } 390 }
398 state_exit() { 391 state_exit() {
399 llSetTimerEvent(0); 392 llSetTimerEvent(0);
400 } 393 }
401 } 394 }
402   395  
403 state tell { 396 state tell {
404 state_entry() { 397 state_entry() {
405 // DEBUG 398 // DEBUG
406 llOwnerSay("[Heartbeat] Sending to group."); 399 llOwnerSay("[Heartbeat] Sending to group.");
407 llInstantMessage( 400 llInstantMessage(
408 wasKeyValueGet( 401 wasKeyValueGet(
409 "corrade", 402 "corrade",
410 configuration 403 configuration
411 ), 404 ),
412 wasKeyValueEncode( 405 wasKeyValueEncode(
413 [ 406 [
414 "command", "tell", 407 "command", "tell",
415 "group", wasURLEscape( 408 "group", wasURLEscape(
416 wasKeyValueGet( 409 wasKeyValueGet(
417 "group", 410 "group",
418 configuration 411 configuration
419 ) 412 )
420 ), 413 ),
421 "password", wasURLEscape( 414 "password", wasURLEscape(
422 wasKeyValueGet( 415 wasKeyValueGet(
423 "password", 416 "password",
424 configuration 417 configuration
425 ) 418 )
426 ), 419 ),
427 "entity", "group", 420 "entity", "group",
428 "message", wasURLEscape(data) 421 "message", wasURLEscape(data)
429 ] 422 ]
430 ) 423 )
431 ); 424 );
432 state listen_group; 425 state listen_group;
433 } 426 }
434 } 427 }
435   428