moderate.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: CC BY 2.0      //
//  Please see: https://creativecommons.org/licenses/by/2.0 for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//                            CONFIGURATION                              //
///////////////////////////////////////////////////////////////////////////
// The UUID / Key of the scripted agent.
string CORRADE = "d726edfb-a1c4-4499-8c01-2746227b55e5";
// The name of the group to invite to.
string GROUP = "[Wizardry and Steamworks]:Support";
// The password for that group in Corrade.ini.
string PASSWORD = "mypassword";
///////////////////////////////////////////////////////////////////////////
//                          END CONFIGURATION                            //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueEncode(list data) {
    list k = llList2ListStrided(data, 0, -1, 2);
    list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
    data = [];
    do {
        data += llList2String(k, 0) + "=" + llList2String(v, 0);
        k = llDeleteSubList(k, 0, 0);
        v = llDeleteSubList(v, 0, 0);
    } while(llGetListLength(k) != 0);
    return llDumpList2String(data, "&");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string k, string data) {
    if(llStringLength(data) == 0) return "";
    if(llStringLength(k) == 0) return "";
    list a = llParseStringKeepNulls(data, ["&", "="], []);
    integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
    if(i != -1) return llList2String(a, 2*i+1);
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
// escapes a string in conformance with RFC1738
string wasURLEscape(string i) {
    string o = "";
    do {
        string c = llGetSubString(i, 0, 0);
        i = llDeleteSubString(i, 0, 0);
        if(c == "") jump continue;
        if(c == " ") {
            o += "+";
            jump continue;
        }
        if(c == "\n") {
            o += "%0D" + llEscapeURL(c);
            jump continue;
        }
        o += llEscapeURL(c);
@continue;
    } while(i != "");
    return o;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
// unescapes a string in conformance with RFC1738
string wasURLUnescape(string i) {
    return llUnescapeURL(
        llDumpList2String(
            llParseString2List(
                llDumpList2String(
                    llParseString2List(
                        i, 
                        ["+"], 
                        []
                    ), 
                    " "
                ), 
                ["%0D%0A"], 
                []
            ), 
            "\n"
        )
    );
}
 
list avatars = [];
list censors = [];
list silence = [];
 
integer line = 0;
string URL = "";
list name = [];
string type = "";
string mute = "";
 
default {
    state_entry() {
        if(llGetInventoryType("moderate") != INVENTORY_NOTECARD) {
            llSetText("Failed to find a notecard named moderate in the primitive's inventory.", <1,0,0>, 1.0);
            return;
        }
        llGetNotecardLine("moderate", line);
    }
    dataserver(key id, string data) {
        if(data == EOF) state geturl;
        if(data == "") jump continue;
        integer i = llSubStringIndex(data, "#");
        if(i != -1) data = llDeleteSubString(data, i, -1);
        if(data == "") jump continue;
        list mod = llParseString2List(data, ["|"], []);
        if(llGetListLength(mod) != 3) jump continue;
        avatars += llStringTrim(llList2String(mod, 0), STRING_TRIM);
        censors += llStringTrim(llList2String(mod, 1), STRING_TRIM);
        silence += llStringTrim(llList2String(mod, 2), STRING_TRIM);
@continue;
        llGetNotecardLine("moderate", ++line);
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state geturl {
    state_entry() {
        llRequestURL();
    }
    http_request(key id, string method, string body) {
        if(method != URL_REQUEST_GRANTED) {
            llSetText("Failed to get an URL.", <1,0,0>, 1.0);
            llSetTimerEvent(60);
            return;
        }
        URL = body;
        state select;
    }
    timer() {
        llResetScript();
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state select {
    state_entry() {
        // for re-entry
        llSetTimerEvent(0.05);
    }
    timer() {
        // if we have completed the list, jump to wait
        if(llGetListLength(avatars) == 0) state wait;
        // stack
        name = llParseString2List(llList2String(avatars, 0), [" "], []);
        avatars = llDeleteSubList(avatars, 0, 0);
        type = llList2String(censors, 0);
        censors = llDeleteSubList(censors, 0, 0);
        mute = llList2String(silence, 0);
        silence = llDeleteSubList(silence, 0, 0);
        // name check
        if(llGetListLength(name) != 2) return;
        // type check
        if(type != "voice" && type != "text") return;
        // mute check
        if(mute != "true" && mute != "false") return;
        llSetText("Moderating: " + llDumpList2String(name, " ") + ", Type: " + type + ", Silent: " + mute, <1, 1, 0>, 1.0);
        // valid at this point
        state moderate;
    }
    state_exit() {
        // cancel timeout
        llSetTimerEvent(0);
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state moderate {
    state_entry() {
        llInstantMessage(CORRADE, 
            wasKeyValueEncode(
                [
                    "command", "moderate",
                    "group", GROUP,
                    "password", PASSWORD,
                    "firstname", llList2String(
                        name,
                        0
                    ),
                    "lastname", llList2String(
                        name,
                        1
                    ),
                    "type", type,
                    "silence", mute,
                    "callback", wasURLEscape(URL)
                ]
            )
        );
        // timeout
        llSetTimerEvent(60);
    }
    http_request(key id, string method, string body) {
        // always confirm to Corrade that the message
        // has been properly received by the callback
        llHTTPResponse(id, 200, "Ok");
        string success = wasURLUnescape(wasKeyValueGet("success", body));
        if(success == "True") 
            llSetText(
                "Moderated: " + llDumpList2String(name, " ") + ", Type: " + type + ", Silent: " + mute, 
                <0,1,0>, 
                1.0
            );
        state select;
    }
    timer() {
        llSetText("Corrade timeout.", <1, 0, 0>, 1.0);
        state select;
    }
    state_exit() {
        // cancel timeout
        llSetTimerEvent(0);
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}
 
state wait {
    state_entry() {
        llSetText("Moderation complate.", <0, 1, 0>, 1.0);
    }
    changed(integer change) {
        if(change & CHANGED_INVENTORY) llResetScript();
    }
}