!7 set flag Matches_fsi. static new L bots; static S whoSaidThat; static new ThreadLocal sendingAnswer; static new ThreadLocal dispatcherAuth; static ThreadLocal> answers = new ThreadLocal; static boolean showMultiResponse = false; static Set reloading = syncSet(); static class Answer { S bot, text; *(S *bot, S *text) {} *() {} } static volatile S loadingBotID; static class Bot { S id; // always formatted boolean enabled, always; double prio; *(S *id, boolean *enabled) {} *() {} } static Map classes = synchroTreeMap(); // wait while a bot is reloading; not used yet static Set gracePeriods = expiringSet(20.0); static volatile long ticks; static volatile boolean tickActionsEnabled = true; p { load("bots"); loadAll(); load("ticks"); load("tickActionsEnabled"); //startTicking(); } static void loadAll() { for (Bot bot : bots) pcall { loadBot(bot); } } static void loadBot(Bot bot) { loadingBotID = bot.id; try { Class c; try { c = hotwire(bot.id); } catch (Throwable e) { throw new RuntimeException("Error in hotwire of " + bot.id, e); } classes.put(bot.id, c); setOpt(c, "mainBot", getMainBot()); try { callMain(c); } catch (Throwable e) { throw new RuntimeException("Error in main of " + bot.id, e); } } finally { loadingBotID = null; } } static Class getSubBot(S id) { ret getBot(id); } static Class getBot(S id) { ret classes.get(formatSnippetID(id)); } static Bot findSubBot(S id) { ret findByField(bots, "id", formatSnippetID(id)); } answer { try { synchronized(main.class) { if "ticks" ret str(ticks); if (master()) { if "enable ticks" { tickActionsEnabled = true; save("tickActionsEnabled"); ret "OK, enabled"; } if "disable ticks" { tickActionsEnabled = false; save("tickActionsEnabled"); ret "OK, disabled"; } } if "count sub bots" ret lstr(getSubBots()); if "full list sub bots" ret slackSnippet(structureLines(getSubBots())); if "list sub bots" { new L notLoaded; L bots = getSubBots(); for (Bot bot : bots) if (!classes.containsKey(bot.id)) notLoaded.add(bot.id); S a = "Enabled: " + structure(collectField(filterByField(bots, "enabled", true), "id")) + ". Disabled: " + structure(collectField(filterByField(bots, "enabled", false), "id")); if (!empty(notLoaded)) a += ". Not loaded: " + structure(notLoaded); ret a; } if "bot prios" { new MultiMap map; for (Bot bot : bots) map.put(bot.prio, bot.id); map.remove(0.0); ret slackSnippet("Listing all but prio 0:\n"+ structureLines(map.data)); } if "is * a sub bot?" ret findSubBot(m.unq(0)) != null ? "yes": "no"; if (master()) { if "add sub bots *" { L bots = (L) safeUnstructure(m.unq(0)); new L out; for (S id : bots) pcall { S q = format("add sub bot *", id); out.add(q + " => "); appendToLast(out, askSelf(q)); } ret slackSnippet(fromLines(out)); } if "add sub bot *" { S id = m.fsi(0); Bot bot = findSubBot(id); if (bot != null) { bots.remove(bot); bots.add(0, bot); save("bots"); ret "OK, bumped."; } bots.add(0, bot = new Bot(id, true)); save("bots"); loadBot(bot); ret format("OK, * loaded & added to top.", id); } if "enable sub bot *" { S id = m.fsi(0); Bot bot = findSubBot(id); if (bot != null) { bot.enabled = true; save("bots"); ret "OK, enabled."; } ret "woot"; } if "disable sub bot *" { S id = m.fsi(0); Bot bot = findSubBot(id); if (bot != null) { bot.enabled = false; save("bots"); ret "OK, disabled."; } ret "woot"; } // an "always bot" is one that should read all msgs if "enable always bot *" { S id = m.fsi(0); Bot bot = findSubBot(id); if (bot != null) { bot.always = true; save("bots"); ret "OK, has status 'always' now."; } ret "woot"; } if "disable always bot *" { S id = m.fsi(0); Bot bot = findSubBot(id); if (bot != null) { bot.always = false; save("bots"); ret "OK, lost status 'always'."; } ret "woot"; } if "prio bot * *" { S id = m.fsi(0); double newPrio = parseDouble(m.unq(1)); Bot bot = findSubBot(id); if (bot != null) { bot.prio = newPrio; save("bots"); ret format("OK, prio changed to *", newPrio); } ret "woot"; } if "list bot *" ret structure(findSubBot(m.unq(0))); if "remove sub bot *" { S id = m.fsi(0); Bot bot = findSubBot(id); if (bot != null) { bots.remove(bot); save("bots"); cleanUp(classes.get(id)); classes.remove(id); ret "OK, removed."; } ret "woot not there anyway"; } if (match("reload sub bot *", s, m) || match("reload sub *", s, m)) { S id = m.fsi(0); long time = sysNow(); Bot bot = findSubBot(id); if (bot != null) { gracePeriods.add(id); temp tempAdd(reloading, id); cleanUp(classes.get(id)); classes.remove(id); loadBot(bot); ret "OK, reloaded in " + renderElapsedTimePleasantly_100ms(elapsedMS(time)); } ret "woot not found"; } } } // dispatch to sub bots O randomChecker = getBot("#1003005"); L dehbots = getSubBots(); sendingAnswer.set(null); answers.set(new L); boolean showMultiResponse = main.showMultiResponse || tb(); for (Bot bot : dehbots) pcall { if (!bot.enabled) continue; Class c = classes.get(bot.id); if (c != null) { //print("Dispatching to " + bot.id); S a = callStaticAnswerMethod(c, s); if (!empty(a)) { sendingAnswer.set(a); answers.get().add(new Answer(bot.id, a)); whoSaidThat = bot.id; if (!showMultiResponse) { informAlwaysBots(subList(dehbots, dehbots.indexOf(bot)+1), s); ret sendingAnswer.get(); } } } } if (showMultiResponse) { new L nonrand; new L rand; for (Answer a : answers.get()) (isRandomBot(randomChecker, a.bot) ? rand : nonrand).add(a); // only show random if there is no non-random response if (empty(nonrand)) nonrand.addAll(rand); L l = nonrand; new L list; if (empty(l)) ret null; if (l(l) == 1) ret first(l).text; for (Answer a : l) setAdd(list, "[" + a.bot + "] " + a.text); ret fromLines(list); } } catch (Throwable e) { printStackTrace(e); ret str(e); } } static synchronized void cleanMeUp() { print("Cleaning up " + l(classes) + " sub-bots"); for (Class c : classes.values()) cleanUp(c); classes.clear(); save("ticks"); } static void informAlwaysBots(L bots, S s) { for (Bot bot : bots) if (bot.always) pcall { Class c = classes.get(bot.id); if (c != null) { S a = callStaticAnswerMethod(c, s); if (!empty(a)) print("Always bot " + bot.id + " said quietly: " + quote(a)); } } } static synchronized L getSubBotIDs() { ret collectField(getSubBots(), "id"); } static synchronized L getSubBots() { ret reversedList(sortedByField(bots, "prio")); /*ret concatLists( filterByField(bots, "lowPrio", false), filterByField(bots, "lowPrio", true));*/ } static Class getClassOfSubBot(S id) { ret classes.get(id); } // loaded / total static int[] numLoaded() { ret new int[] {l(classes), l(bots) }; } static void addSeedBot(S id) { Bot bot = findSubBot(id); if (bot != null) { printF("Seed bot * already there.", id); ret; } bots.add(0, bot = new Bot(id, true)); save("bots"); loadBot(bot); printF("OK, seed bot * loaded & added to top.", id); } static void startTicking() { thread "Ticker" { while true { if (tickActionsEnabled) pcall { tick(); } ++ticks; sleep(250); } } } static void tick() { L dehbots = getSubBots(); for (Bot bot : dehbots) pcall { if (!bot.enabled) continue; Class c = classes.get(bot.id); if (c != null) callOpt(c, "tick"); } } static S getSendingAnswer() { ret sendingAnswer.get(); } static void overwriteAnswer(S a) { sendingAnswer.set(a); } static boolean isRandomBot(O randomChecker, S id) { if (randomChecker == null) ret false; pcall { ret (bool) call(randomChecker, "isRandomBot", id); } ret false; } sbool master() { ret webAuthed() || isTrue(dispatcherAuth!); } sbool isReloading(S botID) { ret contains(reloading, fsIOpt(botID)); }