corrade-lsl-templates – Blame information for rev 41

Subversion Repositories:
Rev:
Rev Author Line No. Line
8 office 1 ///////////////////////////////////////////////////////////////////////////
29 office 2 // Copyright (C) Wizardry and Steamworks 2016 - License: CC BY 2.0 //
8 office 3 ///////////////////////////////////////////////////////////////////////////
4 //
5 // A wiki module that can memorize strings and recall them by path.
6 //
7 ///////////////////////////////////////////////////////////////////////////
8  
9 ///////////////////////////////////////////////////////////////////////////
29 office 10 // Copyright (C) Wizardry and Steamworks 2014 - License: CC BY 2.0 //
8 office 11 ///////////////////////////////////////////////////////////////////////////
12 integer wasIsAlNum(string a) {
13 if(a == "") return FALSE;
14 integer x = llBase64ToInteger("AAAA" +
15 llStringToBase64(llGetSubString(a, 0, 0)));
16 return (x >= 65 && x <= 90) || (x >= 97 && x <= 122) ||
17 (x >= 48 && x <= 57);
18 }
41 office 19  
8 office 20 ///////////////////////////////////////////////////////////////////////////
41 office 21 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
8 office 22 ///////////////////////////////////////////////////////////////////////////
23 string wasKeyValueGet(string k, string data) {
24 if(llStringLength(data) == 0) return "";
25 if(llStringLength(k) == 0) return "";
41 office 26 list a = llParseStringKeepNulls(data, ["&", "="], []);
27 integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
28 if(i != -1) return llList2String(a, 2*i+1);
8 office 29 return "";
30 }
31  
32 ///////////////////////////////////////////////////////////////////////////
29 office 33 // Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0 //
8 office 34 ///////////////////////////////////////////////////////////////////////////
35 string wasKeyValueEncode(list data) {
36 list k = llList2ListStrided(data, 0, -1, 2);
37 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
38 data = [];
39 do {
40 data += llList2String(k, 0) + "=" + llList2String(v, 0);
41 k = llDeleteSubList(k, 0, 0);
42 v = llDeleteSubList(v, 0, 0);
43 } while(llGetListLength(k) != 0);
44 return llDumpList2String(data, "&");
45 }
46  
47 ///////////////////////////////////////////////////////////////////////////
29 office 48 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
8 office 49 ///////////////////////////////////////////////////////////////////////////
50 // escapes a string in conformance with RFC1738
51 string wasURLEscape(string i) {
52 string o = "";
53 do {
54 string c = llGetSubString(i, 0, 0);
55 i = llDeleteSubString(i, 0, 0);
56 if(c == "") jump continue;
57 if(c == " ") {
58 o += "+";
59 jump continue;
60 }
61 if(c == "\n") {
62 o += "%0D" + llEscapeURL(c);
63 jump continue;
64 }
65 o += llEscapeURL(c);
66 @continue;
67 } while(i != "");
68 return o;
69 }
70  
71 ///////////////////////////////////////////////////////////////////////////
29 office 72 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
8 office 73 ///////////////////////////////////////////////////////////////////////////
74 list wasCSVToList(string csv) {
75 list l = [];
76 list s = [];
77 string m = "";
78 do {
79 string a = llGetSubString(csv, 0, 0);
80 csv = llDeleteSubString(csv, 0, 0);
81 if(a == ",") {
82 if(llList2String(s, -1) != "\"") {
83 l += m;
84 m = "";
85 jump continue;
86 }
87 m += a;
88 jump continue;
89 }
90 if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
91 m += a;
92 csv = llDeleteSubString(csv, 0, 0);
93 jump continue;
94 }
95 if(a == "\"") {
96 if(llList2String(s, -1) != a) {
97 s += a;
98 jump continue;
99 }
100 s = llDeleteSubList(s, -1, -1);
101 jump continue;
102 }
103 m += a;
104 @continue;
105 } while(csv != "");
106 // postcondition: length(s) = 0
107 return l + m;
108 }
109  
110 ///////////////////////////////////////////////////////////////////////////
29 office 111 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
8 office 112 ///////////////////////////////////////////////////////////////////////////
113 string wasListToCSV(list l) {
114 list v = [];
115 do {
116 string a = llDumpList2String(
117 llParseStringKeepNulls(
118 llList2String(
119 l,
120  
121 ),
122 ["\""],
123 []
124 ),
125 "\"\""
126 );
127 if(llParseStringKeepNulls(
128 a,
129 [" ", ",", "\n", "\""], []
130 ) !=
131 (list) a
132 ) a = "\"" + a + "\"";
133 v += a;
134 l = llDeleteSubList(l, 0, 0);
135 } while(l != []);
136 return llDumpList2String(v, ",");
137 }
138  
139 ///////////////////////////////////////////////////////////////////////////
29 office 140 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
8 office 141 ///////////////////////////////////////////////////////////////////////////
142 // unescapes a string in conformance with RFC1738
143 string wasURLUnescape(string i) {
144 return llUnescapeURL(
145 llDumpList2String(
146 llParseString2List(
147 llDumpList2String(
148 llParseString2List(
149 i,
150 ["+"],
151 []
152 ),
153 " "
154 ),
155 ["%0D%0A"],
156 []
157 ),
158 "\n"
159 )
160 );
161 }
162  
163 // configuration data
164 string configuration = "";
165 // callback URL
166 string URL = "";
167 // store message over state.
12 office 168 string path = "";
8 office 169 string data = "";
12 office 170 string action = "";
171 string statement = "";
172 string parameters = "";
8 office 173  
174 default {
175 state_entry() {
176 llOwnerSay("[Wiki] Starting...");
177 llSetTimerEvent(10);
178 }
179 link_message(integer sender, integer num, string message, key id) {
180 if(id != "configuration") return;
181 llOwnerSay("[Wiki] Got configuration...");
182 configuration = message;
12 office 183 action = "create";
8 office 184 state url;
185 }
186 timer() {
187 llOwnerSay("[Wiki] Requesting configuration...");
188 llMessageLinked(LINK_THIS, 0, "configuration", NULL_KEY);
189 }
190 on_rez(integer num) {
191 llResetScript();
192 }
193 changed(integer change) {
194 if((change & CHANGED_INVENTORY) ||
195 (change & CHANGED_REGION_START) ||
196 (change & CHANGED_OWNER)) {
197 llResetScript();
198 }
199 }
200 state_exit() {
201 llSetTimerEvent(0);
202 }
203 }
204  
205 state url {
206 state_entry() {
207 // DEBUG
208 llOwnerSay("[Wiki] Requesting URL...");
209 llRequestURL();
210 }
211 http_request(key id, string method, string body) {
212 if(method != URL_REQUEST_GRANTED) return;
213 URL = body;
214 // DEBUG
12 office 215 llOwnerSay("[Wiki] Got URL.");
8 office 216  
12 office 217 if(action == "create") {
218 statement = wasURLEscape("CREATE TABLE IF NOT EXISTS \"" +
219 wasKeyValueGet("wiki table", configuration) +
220 "\" (path text unique collate nocase, data text)");
221 state query;
8 office 222 }
12 office 223  
224 if(action == "get") {
225 statement = wasURLEscape("SELECT data FROM \"" +
226 wasKeyValueGet("wiki table", configuration) +
227 "\" WHERE path=:path");
228 parameters = wasURLEscape(
229 wasListToCSV(
230 [
231 "path",
232 wasURLEscape(path)
233 ]
8 office 234 )
235 );
12 office 236 state query;
8 office 237 }
12 office 238  
239 if(action == "set") {
240 if(data == "") {
241 statement = wasURLEscape("DELETE FROM \"" +
242 wasKeyValueGet("wiki table", configuration) +
243 "\" WHERE path=:path");
244 parameters = wasURLEscape(
245 wasListToCSV(
246 [
247 "path",
248 wasURLEscape(path)
249 ]
250 )
251 );
252 state query;
253 }
254 statement = wasURLEscape("REPLACE INTO \"" +
255 wasKeyValueGet("wiki table", configuration) +
256 "\" (path, data) VALUES (:path, :data)");
257 parameters = wasURLEscape(
258 wasListToCSV(
259 [
260 "path",
261 wasURLEscape(path),
262 "data",
263 wasURLEscape(data)
264 ]
265 )
266 );
267 state query;
268 }
269  
270 if(action == "dir") {
271 if(path == "/") {
272 path = "";
14 office 273 statement = wasURLEscape(
274 "SELECT DISTINCT SUBSTR(path, 1, LENGTH(path) - LENGTH(LTRIM(SUBSTR(path,2), 'abcdefghijklmnopqrstuvwxyz'))) AS path FROM \"" +
275 wasKeyValueGet("wiki table", configuration) +
276 "\" WHERE path LIKE '/%' LIMIT " +
277 wasKeyValueGet("wiki results limit", configuration)
278 );
12 office 279 state query;
280 }
14 office 281 statement = wasURLEscape(
282 "SELECT DISTINCT SUBSTR(REPLACE(path, :base, ''),1, LENGTH(REPLACE(path, :base, '')) - LENGTH(LTRIM(REPLACE(path, :base, ''), 'abcdefghijklmnopqrstuvwxyz'))) AS path FROM \"" +
283 wasKeyValueGet("wiki table", configuration) +
284 "\" WHERE path LIKE :path LIMIT " +
285 wasKeyValueGet("wiki results limit", configuration)
286 );
12 office 287 parameters = wasURLEscape(
288 wasListToCSV(
289 [
290 "path",
291 wasURLEscape(path + "/" + "%"),
292 "base",
14 office 293 wasURLEscape("/" +
294 llDumpList2String(
295 llParseString2List(
296 path,
297 ["/"],
298 []
299 ),
300 "/"
301 ) +
302 "/"
303 )
12 office 304 ]
305 )
306 );
307 state query;
308 }
309  
14 office 310 if(action == "find") {
311 if(data == "") {
312 data = "Command requires two parameters: a path followed by a search term.";
313 state tell;
314 }
315 if(path == "/")
316 path = "";
317 statement = wasURLEscape(
318 "SELECT DISTINCT path FROM \"" +
319 wasKeyValueGet("wiki table", configuration) +
320 "\" WHERE path LIKE :path AND ( data LIKE :data OR path LIKE :data ) COLLATE NOCASE LIMIT " +
321 wasKeyValueGet("wiki search limit", configuration)
322 );
323 parameters = wasURLEscape(
324 wasListToCSV(
325 [
326 "path",
327 wasURLEscape(path + "/" + "%"),
328 "data",
329 wasURLEscape("%" + data + "%")
330 ]
331 )
332 );
333 state query;
334 }
335  
12 office 336 // DEBUG
337 llOwnerSay("[Wiki] Jump table corrupted, please contact creator...");
338 llResetScript();
8 office 339 }
340 on_rez(integer num) {
341 llResetScript();
342 }
343 changed(integer change) {
344 if((change & CHANGED_INVENTORY) ||
345 (change & CHANGED_REGION_START) ||
346 (change & CHANGED_OWNER)) {
347 llResetScript();
348 }
349 }
350 }
351  
352 state listen_group {
353 state_entry() {
354 // DEBUG
355 llOwnerSay("[Wiki] Waiting for group messages...");
356 }
357 link_message(integer sender, integer num, string message, key id) {
358 // We only care about notifications now.
359 if(id != "notification")
360 return;
361  
362 // This script only processes group notifications.
363 if(wasKeyValueGet("type", message) != "group")
364 return;
365  
366 // Get the sent message.
367 data = wasURLUnescape(
368 wasKeyValueGet(
369 "message",
370 message
371 )
372 );
373  
11 office 374 // Check if this is an eggdrop command.
8 office 375 if(llGetSubString(data, 0, 0) !=
376 wasKeyValueGet("command", configuration))
377 return;
11 office 378  
379 // Check if the command matches the current module.
12 office 380 list command = llParseString2List(data, [" "], []);
381 if(llList2String(command, 0) !=
382 wasKeyValueGet("command", configuration) + "wiki")
8 office 383 return;
384  
385 // Remove command.
386 command = llDeleteSubList(command, 0, 0);
387  
388 // Check for supported sub-commands.
389 if(llList2String(command, 0) != "set" &&
390 llList2String(command, 0) != "get" &&
14 office 391 llList2String(command, 0) != "dir" &&
392 llList2String(command, 0) != "find") {
393 data = "Subcommands are: get, set, dir or find";
8 office 394 state tell;
395 }
396  
397 // Get the sub-command and store it as a jump state.
12 office 398 action = llList2String(command, 0);
8 office 399  
400 // Remove sub-command.
401 command = llDeleteSubList(command, 0, 0);
402  
403 // Get the path parts.
404 list path_parts = llParseString2List(
405 llList2String(command, 0), ["/"], []
406 );
407  
408 // Dump the path and store it over states.
409 path = llStringTrim(
410 llDumpList2String(
411 path_parts,
412 "/"
413 ),
414 STRING_TRIM
415 );
416  
417 if(path != "") {
418 integer i = llStringLength(path) - 1;
419 do {
420 string c = llGetSubString(path, i, i);
421 if(c != "/" && !wasIsAlNum(c)) {
422 data = "Only alpha-numerics accepted in the path string.";
423 state tell;
424 }
425 } while(--i > -1);
426 }
427  
428 path = "/" + path;
429  
430 // Remove path.
431 command = llDeleteSubList(command, 0, 0);
432  
433 // Dump the rest of the message.
434 data = llDumpList2String(command, " ");
435  
436 // Get an URL.
437 state url;
438 }
439 on_rez(integer num) {
440 llResetScript();
441 }
442 changed(integer change) {
443 if((change & CHANGED_INVENTORY) ||
444 (change & CHANGED_REGION_START) ||
445 (change & CHANGED_OWNER)) {
446 llResetScript();
447 }
448 }
449 }
450  
12 office 451 state query {
8 office 452 state_entry() {
14 office 453 // Check messge length.
454 string message = wasKeyValueEncode(
455 [
456 "command", "database",
457 "group", wasURLEscape(
458 wasKeyValueGet(
459 "group",
460 configuration
461 )
462 ),
463 "password", wasURLEscape(
464 wasKeyValueGet(
465 "password",
466 configuration
467 )
468 ),
469 "SQL", statement,
470 "data", parameters,
471 "callback", wasURLEscape(URL)
472 ]
473 );
474 // GC - none of these are needed anymore.
475 statement = "";
476 parameters = "";
477 if(llStringLength(message) > 1023) {
478 data = "Message length exceeded 1023 characters.";
479 state tell;
480 }
8 office 481 // DEBUG
12 office 482 llOwnerSay("[Wiki] Executing action: " + action);
8 office 483 llInstantMessage(
484 wasKeyValueGet(
485 "corrade",
486 configuration
487 ),
14 office 488 message
8 office 489 );
14 office 490 // GC
491 message = "";
8 office 492 llSetTimerEvent(60);
493 }
494 http_request(key id, string method, string body) {
495 llHTTPResponse(id, 200, "OK");
496 llReleaseURL(URL);
497 if(wasKeyValueGet("command", body) != "database" ||
498 wasKeyValueGet("success", body) != "True") {
499 // DEBUG
12 office 500 llOwnerSay("[Wiki] Unable query database: " +
8 office 501 wasURLUnescape(
502 wasKeyValueGet("error", body)
503 )
504 );
505 state listen_group;
506 }
12 office 507  
508 // Process actions.
509  
510 if(action == "set") {
511 if(data == "") {
512 data = "Deleted from " + path;
513 state tell;
514 }
515 data = "Stored into " + path;
8 office 516 state tell;
517 }
14 office 518  
519 if(action == "find") {
520 data = llDumpList2String(
521 llList2ListStrided(
522 llDeleteSubList(
523 wasCSVToList(
524 wasURLUnescape(
525 wasKeyValueGet("data", body)
526 )
527 ),
528 0,
529  
530 ),
531 0,
532 -1,
533 2
534 ),
535 ","
536 );
537 if(data == "") {
538 data = "Sorry, the term was not found.";
539 state tell;
540 }
541 state tell;
542 }
543  
12 office 544 if(action == "get") {
545 data = llDumpList2String(
546 llDeleteSubList(
547 wasCSVToList(
548 wasURLUnescape(
549 wasKeyValueGet("data", body)
8 office 550 )
551 ),
12 office 552 0,
553  
554 ),
555 ""
8 office 556 );
557  
12 office 558 if(data == "") {
559 data = "Sorry, that path contains no data.";
560 state tell;
561 }
8 office 562  
12 office 563 data = path + ": " + data;
8 office 564 state tell;
565 }
566  
12 office 567 if(action == "dir") {
568 list paths = llList2ListStrided(
569 llDeleteSubList(
570 wasCSVToList(
571 wasURLUnescape(
572 wasKeyValueGet("data", body)
8 office 573 )
574 ),
12 office 575 0,
576  
8 office 577 ),
578 0,
12 office 579 -1,
580 2
581 );
8 office 582  
12 office 583 if(llGetListLength(paths) == 0) {
584 data = "Sorry, that path contains no sub-paths.";
585 state tell;
586 }
8 office 587  
12 office 588 // Eliminate path component.
589 if(path == "/")
590 path = "";
8 office 591  
12 office 592 list sibling = [];
593 do {
594 // Get the path part.
595 string part = llList2String(paths, 0);
8 office 596  
12 office 597 // Remove the path component.
598 string child = llStringTrim(
599 llDumpList2String(
600 llParseString2List(
601 part,
602 [path, "/"],
603 []
604 ),
605 "/"
8 office 606 ),
12 office 607 STRING_TRIM
8 office 608  
12 office 609 );
8 office 610  
12 office 611 integer i = llSubStringIndex(child, "/");
612 if(i == -1) {
613 sibling += path + "/" + child;
614 jump continue_dir;
615 }
616 child = path + "/" + llDeleteSubString(child, i, -1) + "/";
617 if(llListFindList(sibling, (list)child) == -1)
618 sibling += child;
619 @continue_dir;
620 paths = llDeleteSubList(paths, 0, 0);
621 } while(llGetListLength(paths) != 0);
8 office 622  
12 office 623 data = llList2CSV(sibling);
624 // GC
625 sibling = [];
8 office 626  
12 office 627 state tell;
628 }
629  
630 // Don't announce creating table.
631 if(action == "create")
632 state listen_group;
633  
634 // DEBUG
635 llOwnerSay("[Wiki] Jump table corrupted, please contact creator...");
636 state listen_group;
8 office 637 }
638 timer() {
639 llReleaseURL(URL);
640 state listen_group;
641 }
642 on_rez(integer num) {
643 llResetScript();
644 }
645 changed(integer change) {
646 if((change & CHANGED_INVENTORY) ||
647 (change & CHANGED_REGION_START) ||
648 (change & CHANGED_OWNER)) {
649 llResetScript();
650 }
651 }
652 state_exit() {
653 llSetTimerEvent(0);
654 }
655 }
656  
657 state tell {
658 state_entry() {
659 // DEBUG
660 llOwnerSay("[Wiki] Sending to group.");
661 llInstantMessage(
662 wasKeyValueGet(
663 "corrade",
664 configuration
665 ),
666 wasKeyValueEncode(
667 [
668 "command", "tell",
669 "group", wasURLEscape(
670 wasKeyValueGet(
671 "group",
672 configuration
673 )
674 ),
675 "password", wasURLEscape(
676 wasKeyValueGet(
677 "password",
678 configuration
679 )
680 ),
681 "entity", "group",
682 "message", wasURLEscape(data)
683 ]
684 )
685 );
14 office 686 // GC
687 path = "";
688 data = "";
8 office 689 state listen_group;
690 }
691 }