corrade-lsl-templates – Blame information for rev 29

Subversion Repositories:
Rev:
Rev Author Line No. Line
4 office 1 ///////////////////////////////////////////////////////////////////////////
29 office 2 // Copyright (C) Wizardry and Steamworks 2014 - License: CC BY 2.0 //
4 office 3 ///////////////////////////////////////////////////////////////////////////
4 //
5 // This is a menu selector for the Corrade bot that is capable of replying
6 // to dialog requests while supporting several nested levels of menus. You
7 // can find out more about the Corrade bot by following the URL:
8 // http://was.fm/secondlife/scripted_agents/corrade
9 //
10 // The sit script works together with a "configuration" notecard that must
11 // be placed in the same primitive as this script. The purpose of this
12 // script is to demonstrate answering dialogs with Corrade and you are free
29 office 13 // to use, change, and commercialize it under the CC BY 2.0 license at:
14 // https://creativecommons.org/licenses/by/2.0
4 office 15 //
16 ///////////////////////////////////////////////////////////////////////////
17  
18 ///////////////////////////////////////////////////////////////////////////
29 office 19 // Copyright (C) 2014 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 20 ///////////////////////////////////////////////////////////////////////////
21 string wasKeyValueGet(string k, string data) {
22 if(llStringLength(data) == 0) return "";
23 if(llStringLength(k) == 0) return "";
24 list a = llParseString2List(data, ["&", "="], []);
25 integer i = llListFindList(a, [ k ]);
26 if(i != -1) return llList2String(a, i+1);
27 return "";
28 }
29  
30 ///////////////////////////////////////////////////////////////////////////
29 office 31 // Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 32 ///////////////////////////////////////////////////////////////////////////
33 string wasKeyValueEncode(list data) {
34 list k = llList2ListStrided(data, 0, -1, 2);
35 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
36 data = [];
37 do {
38 data += llList2String(k, 0) + "=" + llList2String(v, 0);
39 k = llDeleteSubList(k, 0, 0);
40 v = llDeleteSubList(v, 0, 0);
41 } while(llGetListLength(k) != 0);
42 return llDumpList2String(data, "&");
43 }
44  
45 ///////////////////////////////////////////////////////////////////////////
29 office 46 // Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 47 ///////////////////////////////////////////////////////////////////////////
48 integer wasListCountExclude(list input, list exclude) {
49 if(llGetListLength(input) == 0) return 0;
50 if(llListFindList(exclude, (list)llList2String(input, 0)) == -1)
51 return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
52 return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
53 }
54  
55 ///////////////////////////////////////////////////////////////////////////
29 office 56 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 57 ///////////////////////////////////////////////////////////////////////////
58 // escapes a string in conformance with RFC1738
59 string wasURLEscape(string i) {
60 string o = "";
61 do {
62 string c = llGetSubString(i, 0, 0);
63 i = llDeleteSubString(i, 0, 0);
64 if(c == "") jump continue;
65 if(c == " ") {
66 o += "+";
67 jump continue;
68 }
69 if(c == "\n") {
70 o += "%0D" + llEscapeURL(c);
71 jump continue;
72 }
73 o += llEscapeURL(c);
74 @continue;
75 } while(i != "");
76 return o;
77 }
78  
79 ///////////////////////////////////////////////////////////////////////////
29 office 80 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 81 ///////////////////////////////////////////////////////////////////////////
82 // unescapes a string in conformance with RFC1738
83 string wasURLUnescape(string i) {
84 return llUnescapeURL(
85 llDumpList2String(
86 llParseString2List(
87 llDumpList2String(
88 llParseString2List(
89 i,
90 ["+"],
91 []
92 ),
93 " "
94 ),
95 ["%0D%0A"],
96 []
97 ),
98 "\n"
99 )
100 );
101 }
102  
103 ///////////////////////////////////////////////////////////////////////////
29 office 104 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
4 office 105 ///////////////////////////////////////////////////////////////////////////
106 list wasCSVToList(string csv) {
107 list l = [];
108 list s = [];
109 string m = "";
110 do {
111 string a = llGetSubString(csv, 0, 0);
112 csv = llDeleteSubString(csv, 0, 0);
113 if(a == ",") {
114 if(llList2String(s, -1) != "\"") {
115 l += m;
116 m = "";
117 jump continue;
118 }
119 m += a;
120 jump continue;
121 }
122 if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
123 m += a;
124 csv = llDeleteSubString(csv, 0, 0);
125 jump continue;
126 }
127 if(a == "\"") {
128 if(llList2String(s, -1) != a) {
129 s += a;
130 jump continue;
131 }
132 s = llDeleteSubList(s, -1, -1);
133 jump continue;
134 }
135 m += a;
136 @continue;
137 } while(csv != "");
138 // postcondition: length(s) = 0
139 return l + m;
140 }
141  
142 // corrade data
143 string CORRADE = "";
144 string GROUP = "";
145 string PASSWORD = "";
146 list RESPONSE = [];
147  
148 // for holding the callback URL
149 string callback = "";
150  
151 // for notecard reading
152 integer line = 0;
153  
154 // key-value data will be read into this list
155 list tuples = [];
156  
157 default {
158 state_entry() {
159 if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
160 llOwnerSay("Sorry, could not find a configuration inventory notecard.");
161 return;
162 }
163 // DEBUG
164 llOwnerSay("Reading configuration file...");
165 llGetNotecardLine("configuration", line);
166 }
167 dataserver(key id, string data) {
168 if(data == EOF) {
169 // invariant, length(tuples) % 2 == 0
170 if(llGetListLength(tuples) % 2 != 0) {
171 llOwnerSay("Error in configuration notecard.");
172 return;
173 }
174 CORRADE = llList2String(
175 tuples,
176 llListFindList(
177 tuples,
178 [
179 "corrade"
180 ]
181 )
182 +1);
183 if(CORRADE == "") {
184 llOwnerSay("Error in configuration notecard: corrade");
185 return;
186 }
187 GROUP = llList2String(
188 tuples,
189 llListFindList(
190 tuples,
191 [
192 "group"
193 ]
194 )
195 +1);
196 if(GROUP == "") {
197 llOwnerSay("Error in configuration notecard: password");
198 return;
199 }
200 PASSWORD = llList2String(
201 tuples,
202 llListFindList(
203 tuples,
204 [
205 "password"
206 ]
207 )
208 +1);
209 if(PASSWORD == "") {
210 llOwnerSay("Error in configuration notecard: password");
211 return;
212 }
213 RESPONSE = wasCSVToList(
214 llList2String(
215 tuples,
216 llListFindList(
217 tuples,
218 [
219 "response"
220 ]
221 )
222 +1)
223 );
224 if(llGetListLength(RESPONSE) == 0) {
225 llOwnerSay("Error in configuration notecard: response");
226 return;
227 }
228 // DEBUG
229 llOwnerSay("Read configuration notecard...");
230 state url;
231 }
232 if(data == "") jump continue;
233 integer i = llSubStringIndex(data, "#");
234 if(i != -1) data = llDeleteSubString(data, i, -1);
235 list o = llParseString2List(data, ["="], []);
236 // get rid of starting and ending quotes
237 string k = llDumpList2String(
238 llParseString2List(
239 llStringTrim(
240 llList2String(
241 o,
242  
243 ),
244 STRING_TRIM),
245 ["\""], []
246 ), "\"");
247 string v = llDumpList2String(
248 llParseString2List(
249 llStringTrim(
250 llList2String(
251 o,
252 1
253 ),
254 STRING_TRIM),
255 ["\""], []
256 ), "\"");
257 if(k == "" || v == "") jump continue;
258 tuples += k;
259 tuples += v;
260 @continue;
261 llGetNotecardLine("configuration", ++line);
262 }
263 on_rez(integer num) {
264 llResetScript();
265 }
266 changed(integer change) {
267 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
268 llResetScript();
269 }
270 }
271 }
272  
273 state url {
274 state_entry() {
275 // DEBUG
276 llOwnerSay("Requesting URL...");
277 llRequestURL();
278 }
279 http_request(key id, string method, string body) {
280 if(method != URL_REQUEST_GRANTED) return;
281 callback = body;
282 // DEBUG
283 llOwnerSay("Got URL...");
284 state detect;
285 }
286 on_rez(integer num) {
287 llResetScript();
288 }
289 changed(integer change) {
290 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
291 llResetScript();
292 }
293 }
294 }
295  
296 state detect {
297 state_entry() {
298 // DEBUG
299 llOwnerSay("Detecting if Corrade is online...");
300 llSetTimerEvent(1);
301 }
302 timer() {
303 llRequestAgentData((key)CORRADE, DATA_ONLINE);
304 }
305 dataserver(key id, string data) {
306 if(data != "1") {
307 // DEBUG
308 llOwnerSay("Corrade is not online, sleeping...");
309 llSetTimerEvent(5);
310 return;
311 }
312 state notify;
313 }
314 on_rez(integer num) {
315 llResetScript();
316 }
317 changed(integer change) {
318 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
319 llResetScript();
320 }
321 }
322 }
323  
324 state notify {
325 state_entry() {
326 // DEBUG
327 llOwnerSay("Binding to the dialog notification...");
328 llInstantMessage(
329 (key)CORRADE,
330 wasKeyValueEncode(
331 [
332 "command", "notify",
333 "group", wasURLEscape(GROUP),
334 "password", wasURLEscape(PASSWORD),
335 "action", "set",
336 "type", "dialog",
337 "URL", wasURLEscape(callback),
338 "callback", wasURLEscape(callback)
339 ]
340 )
341 );
342 llSetTimerEvent(1);
343 }
344 timer() {
345 llRequestAgentData((key)CORRADE, DATA_ONLINE);
346 }
347 dataserver(key id, string data) {
348 if(data != "1") state detect;
349 }
350 http_request(key id, string method, string body) {
351 llHTTPResponse(id, 200, "OK");
352 if(wasKeyValueGet("command", body) != "notify" ||
353 wasKeyValueGet("success", body) != "True") {
354 // DEBUG
355 llOwnerSay("Failed to bind to the dialog notification...");
356 state detect;
357 }
358 // DEBUG
359 llOwnerSay("Dialog notification installed...");
360 state main;
361 }
362 on_rez(integer num) {
363 llResetScript();
364 }
365 changed(integer change) {
366 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
367 llResetScript();
368 }
369 }
370 }
371  
372 state main {
373 state_entry() {
374 // DEBUG
375 llOwnerSay("Waiting for dialog...");
376 llSetTimerEvent(1);
377 }
378 timer() {
379 llRequestAgentData((key)CORRADE, DATA_ONLINE);
380 }
381 dataserver(key id, string data) {
382 if(data != "1") state detect;
383 }
384 http_request(key id, string method, string body) {
385 llHTTPResponse(id, 200, "OK");
386  
387 // get the dialog id
388 key id = (key)wasURLUnescape(
389 wasKeyValueGet(
390 "id",
391 body
392 )
393 );
394  
395 // get the UUID of the object sending the dialog
396 key item = (key)wasURLUnescape(
397 wasKeyValueGet(
398 "item",
399 body
400 )
401 );
402  
403 // get the channel that the dialog was sent on
404 integer channel = (integer)wasURLUnescape(
405 wasKeyValueGet(
406 "channel",
407 body
408 )
409 );
410  
411 // get the buttons that the dialog has
412 list buttons = llList2ListStrided(
413 llDeleteSubList(
414 wasCSVToList(
415 wasURLUnescape(
416 wasKeyValueGet(
417 "button",
418 body
419 )
420 )
421 ),
422 0,
423 1
424 ),
425 0,
426 -1,
427 2
428 );
429  
430 // now find the index of the button in our list of responses
431 integer i = llGetListLength(RESPONSE)-1;
432 integer index = -1;
433 string button = "";
434 do {
435 button = llList2String(RESPONSE, i);
436 index = llListFindList(buttons, (list)button);
437 if(index != -1) jump found;
438 } while(--i>-1);
439 return;
440  
441 @found;
442  
443 llInstantMessage(CORRADE,
444 wasKeyValueEncode(
445 [
446 "command", "replytoscriptdialog",
447 "group", wasURLEscape(GROUP),
448 "password", wasURLEscape(PASSWORD),
449 "action", "reply",
450 "dialog", id,
451 "button", wasURLEscape(button),
452 "index", index
453 ]
454 )
455 );
456 }
457 on_rez(integer num) {
458 llResetScript();
459 }
460 changed(integer change) {
461 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
462 llResetScript();
463 }
464 }
465 }