!752 !include #1002268 // Slack Bot static boolean actuallyPost = true; static S botUserName = "eleu_learns_to_think"; static S actualBotUserName = "eleu"; // token user name static boolean hotwire_over_bot = true; static int maxAnswerLength = 1000, shortenTo = 200; static L botIDs; static new L bots; static new L privBots; // bots with an answerPriv method (privileged/private answering) static new L activeBots; static Map botsByID = synchroTreeMap(); // information for "answer" functions static new ThreadLocal userName; static new ThreadLocal dialogID; static new ThreadLocal actualURI; static volatile boolean loaded; static int webServerPort = 80; // set to 0 for no web serving static long hitCount; static new L history; // stores all processed user questions (not whole chat log) p { if (webServerPort != 0) { readLocally("hitCount"); serveHttp(webServerPort); } initSlackBot(); reload(); readLog(); loaded = true; slackBotLoop(); } static S answerImpl(S s) { new Matches m; if (match("reboot", s)) { thread { sleepSeconds(1); nohupJavax(getProgramID()); System.exit(100); } ret "Rebooting..."; } if (match("reload", s)) { time { reload(); } ret l(bots) + "/" + l(botIDs) + " bots reloaded in "+ getLastTiming() + " ms."; } if (match("reload *", s, m)) { reload(m.unq(0)); ret "Bot " + formatSnippetID(m.unq(0)) + " reloaded."; } if (match("list bots", s)) { S answer = "Active bots: " + structure(activeBots); L inactiveBots = diff(botIDs, activeBots); if (!inactiveBots.isEmpty()) answer += ". Inactive bots: " + structure(inactiveBots); ret answer; } if (match("test", s)) ret "blah!"; ret callStaticAnswerMethod(bots, s); } static synchronized S answer(S s) { long time = now(); S a = "ERREUR"; try { a = answerImpl(s); } finally { pcall { long x = now(); // We should record which bot answered! Map map = litmap("startTime", time, "duration", x-time, "question", s, "answer", a, "botID", "?"); printList(answerPriv(format("log timing *", structure(map)))); } } ret a; } // can give multiple answers static synchronized L answerPriv(S s) { new L l; for (Class bot : privBots) pcall { S a = cast callOpt(bot, "answerPriv", s); if (!empty(a)) l.add(a); } ret l; } static void recordFeedback(S probableQuestion, S answer, S answerTime, S feedback, int score) { if (answer == null || probableQuestion == null) ret; Map data = litmap("realtime", now(), "question", probableQuestion, "answer", answer, "answertime", answerTime, "feedback", feedback, "score", score); print("Recording feedback: " + data); logQuoted(new File(programDir(), "feedback-log"), structure(data)); logQuoted(new File(programDir(), "memory-log"), structure(data)); } static void reloadLists() { botIDs = or((L) loadVariableDefinition("botIDs"), botIDs); generalReleaseBots = or((L) loadVariableDefinition("generalReleaseBots"), generalReleaseBots); botIDs.addAll(generalReleaseBots); } static void reload() { reloadLists(); cleanUp(bots); activeBots = new ArrayList(); privBots = new ArrayList(); botsByID.clear(); for (S botID : botIDs) pcall { loadBot(botID); } } static void reload(S botID) { reloadLists(); S parsedID = "" + parseSnippetID(botID); Class bot = botsByID.get(parsedID); if (bot != null) { cleanUp(bot); botsByID.remove(parsedID); activeBots.remove(formatSnippetID(botID)); bots.remove(bot); privBots.remove(bot); } loadBot(botID); } static void loadBot(S botID) { print("Loading bot: " + botID); Class c = hotwire_over_bot ? hotwire_overBot(botID) : hotwire(botID); setOpt(c, "mainBot", getMainClass()); callMain(c); bots.add(c); activeBots.add(formatSnippetID(botID)); // only if loading doesn't fail botsByID.put("" + parseSnippetID(botID), c); if (hasMethodNamed(c, "answerPriv")) privBots.add(c); print("Loaded bot: " + botID + ", bots now: " + l(activeBots)); } static NanoHTTPD.Response serve(S uri, NanoHTTPD.Method method, Map header, Map parms, Map files) { print("Serving HTTP " + quote(uri)); ++hitCount; saveLocally("hitCount"); actualURI.set(uri); uri = dropPrefixMandatory("/", uri); if (!loaded) ret serveHTML("LOADING, PLEASE TRY AGAIN. " + l(bots) + "/" + l(botIDs) + " bots loaded"); if (eq(uri, "")) ret serveHomePage(); if (eq(uri, "favicon.ico")) ret serve404(); // count and set cookie O visitorsBot = getBot("#1002157"); if (visitorsBot != null) { S visitorHTML = callHtmlMethod(visitorsBot, "/"); } int i = uri.indexOf('/'); S firstPart = i >= 0 ? uri.substring(0, i) : uri; S rest = i >= 0 ? substr(uri, i) : "/"; // rest always starts with "/" ret serveBot(firstPart, rest, parms); } static NanoHTTPD.Response serveBot(S botID, S subUri, Map params) { Class bot = botsByID.get("" + parseSnippetID(botID)); boolean raw = subUri.equals("/raw") || subUri.startsWith("/raw/"); if (raw) { subUri = substr(subUri, "/raw".length()); if (subUri.length() == 0) subUri = "/"; } if (bot != null) { print("Bot " + botID + " methods: " + structure(allMethodNames(bot))); S botHtml = callHtmlMethod(bot, subUri, params); if (raw) ret serveHTML(unnull(botHtml)); if (botHtml == null) botHtml = "Bot has no HTML output."; S title = "Eleutheria Bot " + formatSnippetID(botID); S html = "" + title + "" + "

Bot " + formatSnippetID(botID) + "

\n" + botHtml + "\n"; ret serveHTML(html); } ret serve404(); } static NanoHTTPD.Response serveHomePage() { new StringBuilder buf; buf.append(""); buf.append("Eleutheria Chat Bots"); buf.append(""); buf.append("

Eleutheria Bots

"); buf.append("
    "); for (S botID : concatLists(litlist(getProgramID()), activeBots)) { botID = formatSnippetID(botID); S parsedID = "" + parseSnippetID(botID); S title = "?"; pcall { title = getSnippetTitle_overBot(botID); } S name = botID + " - " + htmlencode(title); buf.append("
  • " + name + " ("); buf.append("source)
  • "); } buf.append("
"); buf.append("

Hit count: " + hitCount + "

"); buf.append("

Last interactions:

"); int maxInteractionsToPrint = 10; for (int i = l(history)-1; i >= 0 && i >= l(history)-maxInteractionsToPrint; i--) { Map m = history.get(i); buf.append("

" + htmlencode(m.get("question")) + "
    " + htmlencode(m.get("answer")) + "

"); } buf.append(""); buf.append(""); ret serveHTML(buf); } static S getSnippetTitle_overBot(S snippetID) { startBot("Snippet Title Bot", "#1001747"); // Use local version //S s = sendToLocalBot(getVMPort() + "/Snippet Title Bot", "what is the title of snippet *", snippetID); // Use bot VM version S s = sendToLocalBot_cached("Snippet Title Bot", "what is the title of snippet *", snippetID); new Matches m; if (match("The title of snippet * is *.", s, m)) ret m.unq(1); throw fail("Bot said: " + s); } static void readLog() { for (S prog : litlist("#1001915", "#1002017")) for (S s : scanLog(prog, "memory-log")) pcall { history.add((Map) safeUnstructure(s)); } print(l(history) + " history entries."); } // for current thread static S getUserName() { ret userName.get(); } // for current thread static S getDialogID() { ret dialogID.get(); } // for current thread static S getActualURI() { ret actualURI.get(); } // for sub-bots to call static O getSession() { ret NanoHTTPD.currentSession.get(); } // for sub-bots to call static O getCookies() { NanoHTTPD.IHTTPSession session = NanoHTTPD.currentSession.get(); NanoHTTPD.CookieHandler cookies = session.getCookies(); ret cookies; } static O getBot(S botID) { ret botsByID.get("" + parseSnippetID(botID)); } static synchronized void historyAdd(Map logEntry) { history.add(logEntry); logQuoted(new File(programDir(), "memory-log"), structure(logEntry)); } static S webAnswer(S s) { O session = getSession(); O cookieHandler = getCookies(); S cookie = cast call(cookieHandler, "read", "cookie"); print("Web answer cookie: " + cookie); print("Web answer question: " + quote(s)); S dialogID = cookie; main.dialogID.set(dialogID); // user name is empty for now S a = null; pcall { a = answer(s, false); // generalRelease = false, all bots print("Web answer: " + quote(a)); if (empty(a)) a = "Hello :)"; } if (empty(a)) a = "(no response)"; Map logEntry = litmap("question", s, "user", "web", "answer", a, "realtime", now()); historyAdd(logEntry); ret a; }