corrade-lsl-templates – Blame information for rev 42

Subversion Repositories:
Rev:
Rev Author Line No. Line
8 office 1 ///////////////////////////////////////////////////////////////////////////
42 office 2 // Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 //
8 office 3 ///////////////////////////////////////////////////////////////////////////
4 //
5 // An eggdrop-like group bot using Corrade.
6 //
7 ///////////////////////////////////////////////////////////////////////////
8  
9 ///////////////////////////////////////////////////////////////////////////
29 office 10 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
8 office 11 ///////////////////////////////////////////////////////////////////////////
12 string wasKeyValueGet(string k, string data) {
13 if(llStringLength(data) == 0) return "";
14 if(llStringLength(k) == 0) return "";
41 office 15 list a = llParseStringKeepNulls(data, ["&", "="], []);
8 office 16 integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
17 if(i != -1) return llList2String(a, 2*i+1);
18 return "";
19 }
42 office 20  
8 office 21 ///////////////////////////////////////////////////////////////////////////
42 office 22 // Copyright (C) 2014 Wizardry and Steamworks - License: CC BY 2.0 //
8 office 23 ///////////////////////////////////////////////////////////////////////////
42 office 24 string wasKeyValueDelete(string k, string data) {
25 if(llStringLength(data) == 0) return "";
26 if(llStringLength(k) == 0) return "";
27 integer i = llListFindList(
28 llList2ListStrided(
29 llParseString2List(data, ["&", "="], []),
30 0, -1, 2
31 ),
32 [ k ]);
33 if(i != -1) return llDumpList2String(
34 llDeleteSubList(
35 llParseString2List(data, ["&"], []),
36 i, i),
37 "&");
38 return data;
39 }
40  
41 ///////////////////////////////////////////////////////////////////////////
42 // Copyright (C) 2014 Wizardry and Steamworks - License: CC BY 2.0 //
43 ///////////////////////////////////////////////////////////////////////////
44 string wasKeyValueSet(string k, string v, string data) {
45 if(llStringLength(k) == 0) return "";
46 if(llStringLength(v) == 0) return "";
47 if(llStringLength(data) == 0) return k + "=" + v;
48 integer i = llListFindList(
49 llList2ListStrided(
50 llParseString2List(data, ["&", "="], []),
51 0, -1, 2
52 ),
53 [ k ]);
54 if(i != -1) return llDumpList2String(
55 llListReplaceList(
56 llParseString2List(data, ["&"], []),
57 [ k + "=" + v ],
58 i, i),
59 "&");
60 return data + "&" + k + "=" + v;
61 }
62  
63 ///////////////////////////////////////////////////////////////////////////
64 // Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3 //
65 ///////////////////////////////////////////////////////////////////////////
8 office 66 string wasKeyValueEncode(list data) {
67 list k = llList2ListStrided(data, 0, -1, 2);
68 list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
69 data = [];
70 do {
71 data += llList2String(k, 0) + "=" + llList2String(v, 0);
72 k = llDeleteSubList(k, 0, 0);
73 v = llDeleteSubList(v, 0, 0);
74 } while(llGetListLength(k) != 0);
75 return llDumpList2String(data, "&");
76 }
77  
78 ///////////////////////////////////////////////////////////////////////////
42 office 79 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
8 office 80 ///////////////////////////////////////////////////////////////////////////
81 // escapes a string in conformance with RFC1738
82 string wasURLEscape(string i) {
83 string o = "";
84 do {
85 string c = llGetSubString(i, 0, 0);
86 i = llDeleteSubString(i, 0, 0);
87 if(c == "") jump continue;
88 if(c == " ") {
89 o += "+";
90 jump continue;
91 }
92 if(c == "\n") {
93 o += "%0D" + llEscapeURL(c);
94 jump continue;
95 }
96 o += llEscapeURL(c);
97 @continue;
98 } while(i != "");
99 return o;
100 }
101  
102 ///////////////////////////////////////////////////////////////////////////
42 office 103 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
8 office 104 ///////////////////////////////////////////////////////////////////////////
105 // unescapes a string in conformance with RFC1738
106 string wasURLUnescape(string i) {
107 return llUnescapeURL(
108 llDumpList2String(
109 llParseString2List(
110 llDumpList2String(
111 llParseString2List(
42 office 112 i,
113 ["+"],
8 office 114 []
42 office 115 ),
8 office 116 " "
42 office 117 ),
118 ["%0D%0A"],
8 office 119 []
42 office 120 ),
8 office 121 "\n"
122 )
123 );
124 }
125  
126 ///////////////////////////////////////////////////////////////////////////
42 office 127 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
8 office 128 ///////////////////////////////////////////////////////////////////////////
129 string wasListToCSV(list l) {
130 list v = [];
131 do {
132 string a = llDumpList2String(
133 llParseStringKeepNulls(
134 llList2String(
42 office 135 l,
8 office 136  
42 office 137 ),
138 ["\""],
8 office 139 []
140 ),
141 "\"\""
142 );
143 if(llParseStringKeepNulls(
42 office 144 a,
8 office 145 [" ", ",", "\n", "\""], []
42 office 146 ) !=
8 office 147 (list) a
148 ) a = "\"" + a + "\"";
149 v += a;
150 l = llDeleteSubList(l, 0, 0);
151 } while(l != []);
152 return llDumpList2String(v, ",");
153 }
154  
42 office 155 ///////////////////////////////////////////////////////////////////////////
156 // Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0 //
157 ///////////////////////////////////////////////////////////////////////////
158 list wasCSVToList(string csv) {
159 list l = [];
160 list s = [];
161 string m = "";
162 do {
163 string a = llGetSubString(csv, 0, 0);
164 csv = llDeleteSubString(csv, 0, 0);
165 if(a == ",") {
166 if(llList2String(s, -1) != "\"") {
167 l += m;
168 m = "";
169 jump continue;
170 }
171 m += a;
172 jump continue;
173 }
174 if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
175 m += a;
176 csv = llDeleteSubString(csv, 0, 0);
177 jump continue;
178 }
179 if(a == "\"") {
180 if(llList2String(s, -1) != a) {
181 s += a;
182 jump continue;
183 }
184 s = llDeleteSubList(s, -1, -1);
185 jump continue;
186 }
187 m += a;
188 @continue;
189 } while(csv != "");
190 // postcondition: length(s) = 0
191 return l + m;
192 }
193  
8 office 194 // for notecard reading
195 integer line = 0;
42 office 196  
8 office 197 // key-value data will be read into this list
198 list tuples = [];
199 string configuration = "";
200 // Corrade's online status.
201 integer online = FALSE;
202 integer compatible = FALSE;
203 string URL = "";
204  
205 // The notifications to bind to.
24 office 206 list notifications = [ "group", "membership", "login", "MQTT" ];
8 office 207  
208 default {
209 state_entry() {
210 if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
211 llOwnerSay("[Control] Sorry, could not find a configuration inventory notecard.");
212 return;
213 }
214 // DEBUG
215 llOwnerSay("[Control] Reading configuration file...");
216 llGetNotecardLine("configuration", line);
217 }
218 dataserver(key id, string data) {
219 if(data == EOF) {
220 // invariant, length(tuples) % 2 == 0
221 if(llGetListLength(tuples) % 2 != 0) {
222 llOwnerSay("[Control] Error in configuration notecard.");
223 return;
224 }
225 key CORRADE = llList2Key(
226 tuples,
227 llListFindList(
42 office 228 tuples,
8 office 229 [
230 "corrade"
231 ]
232 )
233 +1);
234 if(CORRADE == NULL_KEY) {
235 llOwnerSay("[Control] Error in configuration notecard: corrade");
236 return;
237 }
238 string GROUP = llList2String(
239 tuples,
240 llListFindList(
42 office 241 tuples,
8 office 242 [
243 "group"
244 ]
245 )
246 +1);
247 if(GROUP == "") {
248 llOwnerSay("[Control] Error in configuration notecard: group");
249 return;
250 }
251 string PASSWORD = llList2String(
252 tuples,
253 llListFindList(
42 office 254 tuples,
8 office 255 [
256 "password"
257 ]
258 )
259 +1);
260 if(PASSWORD == "") {
261 llOwnerSay("[Control] Error in configuration notecard: password");
262 return;
263 }
264 string VERSION = llList2String(
265 tuples,
266 llListFindList(
42 office 267 tuples,
8 office 268 [
269 "version"
270 ]
271 )
272 +1);
273 if(VERSION == "") {
274 llOwnerSay("[Control] Error in configuration notecard: version");
275 return;
276 }
277 // DEBUG
278 llOwnerSay("[Control] Read configuration notecard...");
279 configuration = wasKeyValueEncode(tuples);
280 // GC
281 tuples = [];
282 state request_url_notifications;
283 }
284 if(data == "") jump continue;
42 office 285 // No support for inline comments for this one! Needs parsing!
8 office 286 integer i = llSubStringIndex(data, "#");
42 office 287 if(i == 0) data = llDeleteSubString(data, i, -1);
8 office 288 list o = llParseString2List(data, ["="], []);
289 // get rid of starting and ending quotes
290 string k = llDumpList2String(
291 llParseString2List(
292 llStringTrim(
293 llList2String(
42 office 294 o,
8 office 295  
42 office 296 ),
297 STRING_TRIM),
8 office 298 ["\""], []
299 ), "\"");
300 string v = llDumpList2String(
301 llParseString2List(
302 llStringTrim(
303 llList2String(
42 office 304 o,
8 office 305 1
42 office 306 ),
307 STRING_TRIM),
8 office 308 ["\""], []
309 ), "\"");
310 if(k == "" || v == "") jump continue;
311 tuples += k;
312 tuples += v;
313 @continue;
314 llGetNotecardLine("configuration", ++line);
315 }
316 attach(key id) {
317 llResetScript();
318 }
319 on_rez(integer num) {
320 llResetScript();
321 }
322 changed(integer change) {
323 if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
324 llResetScript();
325 }
326 }
327 }
328  
329 state request_url_notifications {
330 state_entry() {
331 // DEBUG
332 llOwnerSay("[Control] Requesting URL...");
333 llRequestURL();
334 }
335 http_request(key id, string method, string body) {
336 if(method != URL_REQUEST_GRANTED) return;
337 URL = body;
338 // DEBUG
339 llOwnerSay("[Control] Got URL...");
340 state unbind_notifications;
341 }
342 on_rez(integer num) {
343 llResetScript();
344 }
345 changed(integer change) {
42 office 346 if((change & CHANGED_INVENTORY) ||
347 (change & CHANGED_REGION_START) ||
8 office 348 (change & CHANGED_OWNER)) {
349 llResetScript();
350 }
351 }
352 }
353  
354 state unbind_notifications {
355 state_entry() {
356 // DEBUG
357 llOwnerSay("[Control] Releasing notifications...");
358 llInstantMessage(
359 (key)wasKeyValueGet(
42 office 360 "corrade",
8 office 361 configuration
42 office 362 ),
8 office 363 wasKeyValueEncode(
364 [
365 "command", "notify",
366 "group", wasURLEscape(
367 wasKeyValueGet(
42 office 368 "group",
8 office 369 configuration
370 )
371 ),
372 "password", wasURLEscape(
373 wasKeyValueGet(
42 office 374 "password",
8 office 375 configuration
376 )
377 ),
378 "action", "remove",
11 office 379 "tag", wasURLEscape(
380 wasKeyValueGet(
42 office 381 "notification tag",
11 office 382 configuration
383 )
384 ),
8 office 385 "callback", wasURLEscape(URL)
386 ]
387 )
388 );
389 llSetTimerEvent(60);
390 }
391 http_request(key id, string method, string body) {
392 llHTTPResponse(id, 200, "OK");
393 if(wasKeyValueGet("command", body) != "notify" ||
394 wasKeyValueGet("success", body) != "True") {
395 // DEBUG
42 office 396 llOwnerSay("[Control] Unable to release tag: " +
8 office 397 wasURLUnescape(
398 wasKeyValueGet("error", body)
399 )
400 );
401 llResetScript();
402 }
403 state bind_notifications;
404 }
405 timer() {
406 llOwnerSay("[Control] Timeout releasing notifications");
407 llResetScript();
408 }
409 on_rez(integer num) {
410 llResetScript();
411 }
412 changed(integer change) {
42 office 413 if((change & CHANGED_INVENTORY) ||
414 (change & CHANGED_REGION_START) ||
8 office 415 (change & CHANGED_OWNER)) {
416 llResetScript();
417 }
418 }
419 state_exit() {
420 llSetTimerEvent(0);
421 }
422 }
423  
424 state bind_notifications {
425 state_entry() {
426 // DEBUG
427 llOwnerSay("[Control] Binding to notifications...");
428 llInstantMessage(
429 wasKeyValueGet(
42 office 430 "corrade",
8 office 431 configuration
42 office 432 ),
8 office 433 wasKeyValueEncode(
434 [
435 "command", "notify",
436 "group", wasURLEscape(
437 wasKeyValueGet(
42 office 438 "group",
8 office 439 configuration
440 )
441 ),
442 "password", wasURLEscape(
443 wasKeyValueGet(
42 office 444 "password",
8 office 445 configuration
446 )
447 ),
448 "action", "add",
449 "type", wasURLEscape(
450 wasListToCSV(
451 notifications
452 )
453 ),
454 "URL", wasURLEscape(URL),
11 office 455 "tag", wasURLEscape(
456 wasKeyValueGet(
42 office 457 "notification tag",
11 office 458 configuration
459 )
460 ),
8 office 461 "callback", wasURLEscape(URL)
462 ]
463 )
464 );
465 llSetTimerEvent(60);
466 }
467 http_request(key id, string method, string body) {
468 llHTTPResponse(id, 200, "OK");
469 if(wasKeyValueGet("command", body) != "notify" ||
470 wasKeyValueGet("success", body) != "True") {
471 // DEBUG
42 office 472 llOwnerSay("[Control] Unable to bind notifications: " +
8 office 473 wasURLUnescape(
474 wasKeyValueGet("error", body)
475 )
476 );
477 llResetScript();
478 }
42 office 479 state version_check;
8 office 480 }
481 timer() {
482 llOwnerSay("[Control] Timeout binding notifications");
483 llResetScript();
484 }
485 on_rez(integer num) {
486 llResetScript();
487 }
488 changed(integer change) {
42 office 489 if((change & CHANGED_INVENTORY) ||
490 (change & CHANGED_REGION_START) ||
8 office 491 (change & CHANGED_OWNER)) {
492 llResetScript();
493 }
494 }
495 state_exit() {
496 llSetTimerEvent(0);
497 }
498 }
499  
42 office 500 state version_check {
8 office 501 state_entry() {
502 // DEBUG
503 llOwnerSay("[Control] Checking version...");
504 llInstantMessage(
505 wasKeyValueGet(
506 "corrade",
507 configuration
508 ),
509 wasKeyValueEncode(
510 [
511 "command", "version",
512 "group", wasURLEscape(
513 wasKeyValueGet(
42 office 514 "group",
8 office 515 configuration
516 )
517 ),
518 "password", wasURLEscape(
519 wasKeyValueGet(
42 office 520 "password",
8 office 521 configuration
522 )
523 ),
524 "callback", wasURLEscape(URL)
525 ]
526 )
527 );
528 llSetTimerEvent(60);
529 }
530 http_request(key id, string method, string body) {
531 llHTTPResponse(id, 200, "OK");
532 llSetTimerEvent(0);
42 office 533  
8 office 534 if(wasKeyValueGet("success", body) != "True") {
535 llOwnerSay("[Control] Version check failed...");
536 return;
537 }
42 office 538  
8 office 539 list v = llParseString2List(
540 wasKeyValueGet(
541 "data",
542 body
543 ),
544 ["."],
545 []
546 );
547 integer receivedVersion = (integer)(llList2String(v, 0) + llList2String(v, 1));
548 v = llParseString2List(
549 wasKeyValueGet(
550 "version",
551 configuration
552 ),
553 ["."],
554 []
555 );
556 integer notecardVersion = (integer)(llList2String(v, 0) + llList2String(v, 1));
557 if(receivedVersion < notecardVersion) {
558 llOwnerSay("[Control] Version is incompatible! You need a Corrade of at least version: " +
559 wasKeyValueGet(
560 "version",
561 configuration
562 ) +
11 office 563 " for this template."
8 office 564 );
565 compatible = FALSE;
566 return;
567 }
42 office 568  
569 // Add the URL to the configuration so it can be used for all components.
570 configuration = wasKeyValueSet("URL", URL, configuration);
571  
8 office 572 // DEBUG
573 llOwnerSay("[Control] Version is compatible!");
574 compatible = TRUE;
42 office 575  
576 state serve_configuration;
8 office 577 }
578 timer() {
579 llOwnerSay("[Control] Timeout checking version...");
580 llResetScript();
581 }
582 on_rez(integer num) {
583 llResetScript();
584 }
585 changed(integer change) {
42 office 586 if((change & CHANGED_INVENTORY) ||
587 (change & CHANGED_REGION_START) ||
8 office 588 (change & CHANGED_OWNER)) {
589 llResetScript();
590 }
591 }
592 state_exit() {
593 llSetTimerEvent(0);
594 }
595 }
42 office 596  
597 state serve_configuration {
598 state_entry() {
599 // DEBUG
600 llOwnerSay("[Control] Serving configuration and passing callbacks...");
601 }
602 http_request(key id, string method, string body) {
603 llHTTPResponse(id, 200, "OK");
604  
605 if(wasKeyValueGet("command", body) != "") {
606 llMessageLinked(LINK_THIS, 0, body, "callback");
607 return;
608 }
609  
610 // Check if this group message is from Corrade and passed through Discord.
611 if(wasKeyValueGet("type", body) == "group" &&
612 llToUpper(wasKeyValueGet("agent", body)) == llToUpper(wasKeyValueGet("corrade", configuration)) &&
613 llSubStringIndex(wasURLUnescape(wasKeyValueGet("message", body)), "[Discord]:") != -1) {
614  
615 // Split message in Discord discriminator and message body.
616 list messageSplit = llParseString2List(
617 wasURLUnescape(
618 wasKeyValueGet("message", body)
619 ),
620 ["[Discord]:"],
621 []
622 );
623  
624 // Retrive the Discord discriminator.
625 string did = llStringTrim(
626 llList2String(
627 messageSplit,
628  
629 ),
630 STRING_TRIM
631 );
632  
633  
634 // Retrieve a list of Discord discriminator that are allowed to send administrative commands.
635 list admins = wasCSVToList(wasKeyValueGet("discord admin", configuration));
636  
637 // If the sender is not amongst the administrative Discord discriminators, then strip the agent details.
638 if(llListFindList(admins, [ did ]) == -1) {
639 // DEBUG
640 llOwnerSay("[Control] Non admin, issuing command, strip the agent details from the command.");
641  
642 body = wasKeyValueDelete("firstname", body);
643 body = wasKeyValueDelete("lastname", body);
644 body = wasKeyValueDelete("agent", body);
645 }
646  
647 // Pack the message back without the Discord discriminator identifier.
648 body = wasKeyValueSet(
649 "message",
650 wasURLEscape(
651 llStringTrim(
652 llList2String(
653 messageSplit,
654 1
655 ),
656 STRING_TRIM
657 )
658 ),
659 body
660 );
661 }
662  
663 llMessageLinked(LINK_THIS, 0, body, "notification");
664 }
665 link_message(integer sender, integer num, string message, key id) {
666 if(message != "configuration") return;
667  
668 llMessageLinked(LINK_THIS, 0, configuration, "configuration");
669 }
670 on_rez(integer num) {
671 llResetScript();
672 }
673 changed(integer change) {
674 if((change & CHANGED_INVENTORY) ||
675 (change & CHANGED_REGION_START) ||
676 (change & CHANGED_OWNER)) {
677 llResetScript();
678 }
679 }
680 state_exit() {
681 llSetTimerEvent(0);
682 }
683 }