!7 concept ScriptClass > Named {} concept BotStatus { bool isPublic; // defaulting to false, user can say "go public" } cmodule ChatBotFrontend > DynChatBotFrontend { switchable bool keepLoaded; Set scriptRefs = syncSet(); transient L scripts; transient CRUD scriptsCRUD; transient S[] exportedFunctions = litstringarray('setBotName, 'getBotName, 'makeBotPublic, 'isBotPublic, 'chatLog_userMessagesOnly); // TODO bool isLegitScriptID(S scriptID) { new Matches m; ret startsWithOneOf(scriptID, m, "#1027663/SomeCruddieScripts/", "#1027704/SomeCruddieScripts/", ) && isIdentifier(m.rest()); } start { scriptsCRUD = new CRUD(cc, ScriptClass); thread "Load Scripts" { while (dm_booting()) sleepSeconds(0.1); loadScripts(); } dm_registerAs chatBotFrontend(); } afterVisualize { addTab(tabs, "Scripts", scriptsCRUD.visualize()); } void loadScripts enter { new L scripts; for (S ref : (LS) collect name(list(ScriptClass))) pcall { LS l = splitAtSlash(ref); if (l(l) != 3) continue with print("Can't load script: " + ref); S mod = dm_require(first(l) + "/" + second(l)); Class c = dm_getClassInModuleRealm(mod, "main$" + last(l)); if (c == null) continue with print("Couldn't find class: " + ref); O o = nu(c); forwardSwappableFunctionsToObject(o, module(), exportedFunctions); setOpt(o, deleteMe := r { removeScript(ref) }); scripts.add(o); print("Got script: " + o); } this.scripts = scripts; } @Override S answer_other(S s) null { new Matches m; if "clear scripts" { deleteConcepts(cc, ScriptClass); ret "OK"; } if "add script *" { if (!isLegitScriptID($1)) ret "Not a legit script ID"; addScripts(ll($1)); ret "OK"; } if "list scripts" ret "[don't say]" + or2(lines(collect name(list(ScriptClass))), "No scripts loaded"); } @Override S answer_other_lowPrio(S s) null { fOr (virtual CruddieScript script : scripts) pcall { try S a = (S) call(script, 'answer, s); } } @Override O[] popDownButtonParams() { ret objectArrayPlus(super.popDownButtonParams(), "Load scripts", rThread loadScripts); } // API void addScripts(LS scriptIDs) { bool change; fOr (S id : scriptIDs) if (uniq_trueIfNew(cc, ScriptClass, name := addPrefix("#", id))) set change; if (change) loadScripts(); } void addScript(S moduleLibID, S shortClassName) { if (uniq_trueIfNew(cc, ScriptClass, name := addPrefix("#", moduleLibID) + "/" + assertIdentifier(shortClassName))) loadScripts(); } void removeScript(S scriptID) { deleteConceptsWhere(cc, ScriptClass, name := scriptID); } // timeout is in seconds bool shouldStayLoaded(double timeout) { ret keepLoaded // user wants this module to stay loaded || nempty(currentActivities) // module is doing something rn || elapsedSeconds_sysTime(lastAccessed) <= timeout; // used recently } BotStatus botStatus() { ret uniq(cc, BotStatus); } void makeBotPublic(bool isPublic) { cset(botStatus(), +isPublic); } bool isBotPublic() { ret botStatus().isPublic; } swappable L/*S*/ chatLog_userMessagesOnly() { null; } }