!7 set flag DynModule. // for transpilation //set flag dm_evalJava_withModule_debug. //set flag veryQuickJava3_debug. // store bot data per conversation concept Conversation { S cookie; S dataStruct; } cmodule2 GazelleMultiBot > DynGazelleBot { switchable int maxEvalResultLength = oneMegabyte_int(); switchable double evalTimeout = 30.0; switchable long botProcessed; // timestamp of last post processed by bots switchable int maxBotAnswerLength = 100000; switchable bool runAutoBots = true; switchable int maxRuntimeMinutes = 60; transient new L bots; Map allPosts = syncTreeMap(); transient Map loadedCodePosts = syncTreeMap(); transient new O couldntLoadCode; transient Lock codeLoadLock = lock(); set flag NoNanoHTTPD. !include #1029545 // API for Eleu class Bot { S name; *() {} *(S *name) {} *(S *name, IVF1 *handlePost) {} swappable void handlePost(GazellePost post) {} GazelleBotCred cred() { ret GazelleBotCred(_user, _botToken, name); } void postReply(GazellePost post, S text, S type, S title default null) { if (empty(text) && empty(title)) text = ""; gazelle_createPost(cred(), text, type, refs := post.id, +title); } void createPostFromBotResult(GazellePost post, IF0 f, IF1 modifyBotInfo default null) { S text, type = "Code Result", title = "", botInfo = ""; try { O result = f!; if (result cast CreatePost) { O[] params = toObjectArray(result.params); text = (S) optPar text(params); type = (S) optPar type(params, type); title = (S) optPar title(params); botInfo = (S) optPar botInfo(params); } else text = str_shortenSyntheticAndStandardToString(result); } catch print e { text = getStackTrace(e); } if (empty(text) && empty(title)) text = ""; botInfo = callFOrKeep(modifyBotInfo, botInfo); GazelleBotCred cred = cred(); if (nempty(botInfo)) cred.botInfo = botInfo; gazelle_createPost(cred, text, type, +title, refs := post.id); } } srecord CreatePost(L params) {} start { dm_useLocalMechListCopies(); dbIndexing(Conversation, "cookie"); grabLoop.handlePosts = posts -> { dm_mediumRefreshTranspiler(); grabLoop.handlePosts_base(posts); for (GazellePost post : posts) setField(botProcessed := max(botProcessed, post.modified)); }; // legacy conversion to sort allPosts setField_noCheck(allPosts := asSyncTreeMap(allPosts)); bots.add(new Bot("Math Bot", post -> { if (post.creating) ret; gazelle_mathBot1_handlePost_2(_user, _botToken, post); })); bots.add(new Bot("Code Safety Checker") { void handlePost(GazellePost post) { if (post.creating) ret; if (post.isJavaXCode()) gazelle_createPost(cred(), codeSafetyCheckResult(post.text), "Code Safety", refs := post.id); } }); bots.add(new Bot("Safe Code Runner") { void handlePost(GazellePost post) { if (post.creating) ret; if (post.isJavaXCode()) { int runtime = min(maxRuntimeMinutes, parseFirstInt(jextractIC("runtime minutes", post.type))); S code = post.text; if (isCodeSafe(code)) { S _code = prepareCode(code, post); //S out = shorten(maxEvalResultLength, runCode(code)); createPostFromBotResult(post, () -> evalCode(runtime*60.0, _code, post)); } } } }); bots.add(new Bot("Run Code On All Posts") { void handlePost(GazellePost post) { if (post.creating) ret; if (eqic(post.type, "Instruction") && match("Please run this code on all posts", post.text)) { long ref = gazelle_firstPostRef(post.id); if (ref == 0) ret; S code = gazelle_text(ref); if (!isCodeSafe(code)) ret; S code2 = "ret func(S post) { " + code + " };"; O function = evalCode(code2); new LS lines; S out = shorten(maxEvalResultLength, runCode(code)); for (GazellePost post2 : cloneValues(allPosts)) { lines.add("Post " + post2.id + " (" + quote(shorten(20, post2.text)) + "): " + shorten(80, runFunc(() -> callF(function, post2.text)))); } gazelle_createPost(cred(), lines(lines), "Code Result", refs := post.id); } } }); bots.add(new Bot("Mark identifiers safe") { void handlePost(GazellePost post) { if (post.creating) ret; if (/*eqic(post.type, "Instruction") &&*/ post.creator.isMaster && match("Mark safe", post.text)) { S text = getPost(first(post.postRefs)).text; LS ids = tok_identifiersInOrder(regexpFirstGroupIC("Unknown identifiers: (.+)", text)); print("Marking safe: " + ids); postReply(post, markSafe(ids), "Marked safe"); } } }); bots.add(new Bot("Post Deleter") { void handlePost(GazellePost post) { if (post.creating) ret; if (/*eqic(post.type, "Instruction") &&*/ post.creator.isMaster && match("Delete posts", post.text)) { long ref = gazelle_firstPostRef(post.id); if (ref == 0) ret; S text = gazelle_text(ref); L postIDs = allToLong(regexpAllFirstGroups(gazelle_deletePostRegexp(), text)); print("Deleting posts: " + postIDs); if (nempty(postIDs)) { Map result = gazelle_deletePosts(cred(), postIDs); postReply(post, str(result), "Deletion result"); } else postReply(post, "No mentioned posts found", "Deletion result"); } } }); bots.add(new Bot("Detector Runner") { void handlePost(GazellePost post) { if (post.creating) ret; ret unless eqic(post.type, "Instruction") && match("Please run detector", post.text); try { long detectorID = post.refWithTagOrFail("detector"); long posExamplesID = post.refWithTagOrFail("positive examples"); long negExamplesID = post.refWithTagOrFail("negative examples"); S code = getPost(detectorID).text; LS posExamples = tlft(getPost(posExamplesID).text); LS negExamples = tlft(getPost(negExamplesID).text); LPair examples = trueFalseBPairs(posExamples, negExamples); if (!isCodeSafe(code)) fail("Detector code not safe"); IF1 detector = proxy IF1(evalCode(code)); new LS good; new LS bad; new Scorer scorer; long time = sysNow(); evalWithTimeoutOrFail(evalTimeout, r { for (Pair example : examples) { S result = runFunc(() -> detector.get(example.a)); bool ok = eq(result, str(example.b)); scorer.add(ok); S line = (ok ? "OK" : example.b ? "False negative" : "False positive") + " (" + shorten(10, result) + "): " + example.a; (ok ? good : bad).add(line); } }); time = sysNow()-time; S text = "Detector code:\n\n" + indentx(code) + "\n\n" + n2(good, "correct answer") + ", " + n2(bad, "bad answer") + ". Runtime: " + n2(time) + " ms\n\n" + or2(trim(lines(concatLists(bad, ll(""), good))), "No errors"); S title = "Score for detector " + detectorID + ": " + scorer; gazelle_createPost(cred(), text, "Detector Score", +title, refs := joinWithSpace(ll(post.id, detectorID, posExamplesID, negExamplesID)), refTags := linesLL_rtrim("", "detector", "positive examples", "negative examples")); } catch e { postReply(post, getStackTrace(e), "Error"); } } }); bots.add(new Bot("Auto Bot Runner") { void handlePost(GazellePost post) { if (!runAutoBots) ret; if (post.creating) ret; if (post.isAutoBotMade()) ret; print("Auto Bot Runner: " + post.id); for (GazellePost botPost : values(allPosts)) { continue unless eqic(botPost.type, "JavaX Code (Bot run on every post)") && botPost.creator.isMaster; print("Processing auto-bot " + botPost.id + " on " + post.id); O code = codeForPost(botPost); if (code == couldntLoadCode) continue with print("Couldn't load code for auto-bot " + botPost.id); print("Code type: " + className(code)); O botInstance = evalWithTimeoutOrFail(evalTimeout, () -> callFOrNewInstance(code)); print("Bot instance type: " + className(botInstance)); setOpt(botInstance, post := post.text); createPostFromBotResult(post, () -> call(botInstance, "calc"), botInfo -> joinNemptiesWithColon("Auto-Bot " + botPost.id, botInfo)); } } }); dm_doEvery(60.0, 3600.0, r removeDeletedPosts); } void handlePost(GazellePost post) { allPosts.put(post.id, post); change(); loadedCodePosts.remove(post.id); if (post.modified > botProcessed) { print("modified: " + post.modified + "/" + botProcessed); for (Bot bot : bots) pcall { bot.handlePost(post); } } } // if post != null, store transpilation O evalCode(S code, GazellePost post default null) { ret evalCode(evalTimeout, code, post); } O evalCode(double timeout, S code, GazellePost post default null) { printWithIndent("CODE> ", code); veryQuickJava_transpiled.set(post != null ? "" : null); // request transpilation try { ret dm_javaEvalWithTimeout(timeout, code); } finally { if (post != null) { S java = veryQuickJava_transpiled!; print("Transpilation for " + post.id + ": " + shorten(java)); saveTextFile(transpilationFile(post.id), nullOnEmpty(java)); } } } // assumes code is safety-checked S runCode(S code) { printWithIndent("CODE> ", code); ret runFunc(() -> str_shortenSyntheticAndStandardToString(dm_javaEval(code))); } // run IF0 with timeout, exception to string, convert result to string S runFunc(IF0 f) { ret str_shortenSyntheticAndStandardToString(evalWithTimeoutOrException(evalTimeout, func { try { ret str_shortenSyntheticAndStandardToString(f!); } catch e { ret getStackTrace(e); } })); } L repliesTo(GazellePost post) { ret filter(values(allPosts), p -> contains(p.postRefs, post.id)); } L repliesWithTag(GazellePost post, S tag) { Pair pair = pair(post.id, upper(tag)); ret filter(repliesTo(post), p -> contains(mapPairsB toUpper(p.taggedRefs()), pair)); } GazellePost getPost(long id) { ret allPosts.get(id); } S getPostText(long id) { ret getPost(id).text; } Cl getAllPosts() { ret values(allPosts); } CreatePost createPost(O... _) { ret new CreatePost(asList(_)); } CodeSafetyChecker codeSafetyChecker() { new CodeSafetyChecker checker; checker.init(); checker.markSafe("getAllPosts"); ret checker; } S codeSafetyCheckResult(S code) { CodeSafetyChecker checker = codeSafetyChecker(); checker.checkCode(code); ret checker.verbalCheckResult(); } bool isCodeSafe(S code) { CodeSafetyChecker checker = codeSafetyChecker(); checker.checkCode(code); ret checker.isSafe(); } S prepareCode(S code, GazellePost post) { if (tok_isStaticLevelCode(code)) ret code; code = tok_addReturn(code); // define implicit vars and functions // post = text of parent post if (containsJavaToken(code, "post")) { long ref = gazelle_firstPostRef(post.id); if (ref != 0) code = "S post = " + quote(gazelle_text(ref)) + ";\n" + code; } // post = text of grandparent post if (containsJavaToken(code, "post2")) { long ref = gazelle_firstPostRef(gazelle_firstPostRef(post.id)); if (ref != 0) code = "S post2 = " + quote(gazelle_text(ref)) + ";\n" + code; } if (containsJavaToken(code, "getAllPosts")) code = [[ embedded Cl getAllPosts() { ret lazyMap_bitSet quickImport(asList((Cl) dm_call(dm_current_generic(), "getAllPosts"))); } ]] + code; if (containsJavaToken(code, "createPost")) code = [[ embedded O createPost(O... _) { ret dm_call(dm_current_generic(), "createPost", _); } ]] + code; // optimize gazelle_text if (containsJavaToken(code, "gazelle_text")) { code = [[ embedded S gazelle_text(long id) { ret (S) dm_call(dm_current_generic(), "getPostText", id); } ]] + code; } ret code; } O html(IWebRequest req) { new Matches m; if (eqic(req.uri(), "/favicon.ico")) ret serveFile(loadLibrary(#1400439), faviconMimeType()); if (startsWith(req.uri(), "/htmlBot/", m) && isInteger(m.rest())) { req.noSpam(); long postID = parseLong(m.rest()); GazellePost post = getPost(postID); if (post == null) ret serve404("Post " + postID + " not found"); O code = codeForPost(post); IF0 calc; // Case 1: Static page (code just returns a string) if (code instanceof S) calc = () -> code; // Case 2: Code is an argumentless function else if (implementsInterfaceShortNamed IF0(code)) calc = toIF0(code); else { // Case 3: Code is IF1 // Sadly, for a lambda like (IF1) req -> ..., we can't find the // IWebRequest type by reflection. So we find the IWebRequest interface by name. Class reqType = getClassInRealm("main$IWebRequest", code); print(+reqType); if (reqType == null) fail("IWebRequest not found in bot"); O wrappedReq = proxy(reqType, req); calc = () -> callF(code, wrappedReq); } O result = evalWithTimeoutOrFail(evalTimeout, () -> calc!); ret (S) result; } if (startsWith(req.uri(), "/chatBotReply/", m) && isInteger(m.rest())) { req.noSpam(); long postID = parseLong(m.rest()); GazellePost post = getPost(postID); if (post == null) ret serve404("Post " + postID + " not found"); O code = codeForPost(post); if (code == couldntLoadCode) ret withHeader(serve500("Couldn't load code for post")); S q = req.params().get("q"); // user input bool initial = eq(req.params().get("initial"), "1"); S cookie = req.params().get("cookie"); if (!initial && empty(q)) ret withHeader(serveText("")); S answer = ""; if (implementsInterfaceShortNamed IF1(code)) { // stateless bot if (!initial) answer = evalWithTimeoutOrFail(evalTimeout, () -> strOrEmpty(callF(code, q))); } else { print("Stateful bot. cookie: " + cookie); if (empty(cookie)) ret withHeader(serve500("Need cookie for stateful bot")); Conversation conv = uniq(Conversation, +cookie); O instance = code; if (instance == null) ret withHeader(serve500("No bot instance")); print("Stateful bot instance: " + instance); if (nempty(conv.dataStruct)) { O data = unstructureInRealm(conv.dataStruct, instance); // hopefully this is safe print("Unstructured: " + data); copyAllThisDollarFields(instance, data); // probably not needed anymore instance = data; } O _instance = instance; answer = evalWithTimeoutOrFail(evalTimeout, () -> strOrEmpty( initial ? callOpt(_instance, "initialMessage") : call(_instance, "answer", q))); cset(conv, dataStruct := structure(instance)); print("Structured: " + conv.dataStruct); } if (initial && empty(answer)) answer = "Bot " + post.id + " ready"; ret withHeader(serveJSON(litorderedmap(answer := shorten(maxBotAnswerLength, answer)))); } if (startsWith(req.uri(), "/transpilation/", m) && isInteger(m.rest())) { long postID = parseLong(m.rest()); GazellePost post = getPost(postID); if (post == null) ret serve404("Post " + postID + " not found"); S src = loadTextFile(transpilationFile(post.id)); if (empty(src)) src = "No transpilation found for post " + postID + "." + (!post.isJavaXCode() ? " Code is not a code post." : " Please try \"Touch post\" and wait a few seconds"); else pcall { src = javaPrettyPrint(src); } ret serveText(src); } if (eq(req.uri(), "/")) { ret "Loaded code posts:" + ul(keys(loadedCodePosts)); } ret serve404(); } O codeForPost(GazellePost post) { lock codeLoadLock; O code = loadedCodePosts.get(post.id); if (code == null) { try { S codeText = post.text; if (!isCodeSafe(codeText)) fail("Code is not safe: " + codeSafetyCheckResult(codeText)); codeText = prepareCode(codeText, null); code = evalCode(codeText, post); } catch print e { code = couldntLoadCode; } loadedCodePosts.put(post.id, code); } ret code; } File transpilationFile(long postID) { ret programFile("Post-Transpilations/" + postID + ".java"); } O withHeader(O response) { call(response, 'addHeader, "Access-Control-Allow-Origin", "*"); ret response; } O _getReloadData() { ret loadedCodePosts; } void _setReloadData(Map data) { if (data != null) loadedCodePosts = data; } void forgetLoadedCodePosts { clear(loadedCodePosts); } void enhanceFrame(Container f) { super.enhanceFrame(f); internalFramePopupMenuItems(f, "Forget loaded code", rEnter forgetLoadedCodePosts, "Remove deleted posts", rThreadEnter removeDeletedPosts); } void removeDeletedPosts { Cl posts = asSet(grabLoop.allPostIDs()); print("Keeping " + nPosts(posts)); if (syncRemoveAllExcept(allPosts, posts)) change(); print("New count: " + nPosts(allPosts)); removeAllExcept(loadedCodePosts, posts); } }