!7 sS dbBotID = #1026323; static Env env; static new ThreadLocal session; static new ThreadLocal out; sclass Out { S buttonsIntro; LS buttons; S placeholder; S defaultInput; } sbool testFunctions = false; static interface Env { void sayAsync(S session, S msg); } concept Session { S cookie; S language; S lastQuestion; FormInFlight form, lastForm; void cancelForm { cset(this, lastForm := form, form := null); } S language() { ret or2(language, 'en); } } sclass FormStep { S key; S displayText, desc; S defaultValue; S placeholder; // null for same as displayText, "" for none LS buttons; S value; void update(Runnable onChange) {} } sclass FormInFlight { S hashTag; new L steps; int stepIndex; // in steps list FormStep currentStep() { ret get(steps, stepIndex); } void update(Runnable onChange) { if (currentStep() != null) currentStep().update(onChange); } S complete() { ret "Form complete"; } S cancel() { ret "Request cancelled"; } bool shouldCancel() { false; } } sclass MainForm > FormInFlight { FormStep stepInterested, stepMailAddress, stepDevice, stepConfirm; *() { hashTag = "#mainForm"; } bool shouldCancel() { ret isNo(stepInterested.value); } } sbool debug; svoid setSession(S cookie, SS params) { session.set(uniq_sync(Session, +cookie)); S lang = mapGet(params, 'language); if (nempty(lang)) cset(session!, language := lang); S lang_default = mapGet(params, 'language_default); if (nempty(lang_default)) { print("lang_default=" + lang_default + ", have: " + session->language); if (empty(session->language)) { print("Setting language."); cset(session!, language := lang_default); } } } svoid clearSession(S cookie) { deleteConcepts(Session, +cookie); } p { db(); // for testing on desktop if (isMain()) { veryBigConsole(); bot(); } } sS returnQuestion(S q) { cset(session!, lastQuestion := q); ret q; } sS answer(S s) { if (session! == null) setSession("default", new Map); out.set(new Out); if (creator() == null) if "debug" set debug; S lq = session->lastQuestion; cset(session!, lastQuestion := null); FormInFlight form = session->form; if (form != null && form.currentStep() != null) { if (eqicOneOf(s, "cancel", "Abbrechen", unicode_crossProduct())) { S answer = form.cancel(); session->cancelForm(); ret answer; } else if (eqicOneOf(s, "back", "zurück", unicode_undoArrow()) && form.stepIndex > 0) { --form.stepIndex; session->change(); ret deliverAnswerAndFormStep(""); } else { FormStep step = form.currentStep(); if (nempty(step.buttons) && !cic(step.buttons, s)) ret deliverAnswerAndFormStep(de() ? "Bitte wählen Sie eine Option!" : "Please choose an option."); step.value = s; ++form.stepIndex; session->change(); if (form.currentStep() == null) { S answer = form.complete(); session->cancelForm(); ret answer; } ret deliverAnswerAndFormStep(""); } } if (testFunctions) { if (eq(lq, "Would you like some tea?")) { if "yes" ret "Here's your tea."; if "no" ret "Very well then."; } if "test buttons" { out->buttons = ll("Yes", "No"); ret returnQuestion("Would you like some tea?"); } if "say something later" { if (env == null) ret("No env"); final S mySession = session->cookie; thread { sleepSeconds(5); env.sayAsync(mySession, "Here it is."); } ret "OK, will do it in 5 seconds"; } } // call special input processors try answer processURLInInput(s); // get answer from bot, switch language O bot = dbBot(); S a = (S) call(bot, 'answer, s, session->language()); S lang = cast getThreadLocal(((ThreadLocal) get(bot, 'language_out))); if (nempty(lang)) cset(session!, language := lang); S a2 = replaceAll(a, "\\s*#mainForm\\b", ""); if (neq(a, a2)) { form = new MainForm; print("Made form for cookie " + session->cookie + ": " + sfu(form)); cset(session!, +form); } ret deliverAnswerAndFormStep(a2); } sS deliverAnswerAndFormStep(S answer) { FormInFlight form = session->form; if (form == null) ret answer; form.update(r { session->change() }); if (form.shouldCancel()) { session->cancelForm(); ret answer; } if (form.currentStep() == null) ret answer; FormStep step = form.currentStep(); if (empty(answer)) answer = step.displayText; else out->buttonsIntro = step.displayText; out->placeholder = or(step.placeholder, step.displayText); print("Step " + form.stepIndex + ": " + sfu(step)); out->defaultInput = or2(step.value, step.defaultValue); out->buttons = cloneList(step.buttons); if (form.stepIndex > 0) out->buttons.add(de() ? "Zurück" : "Back"); out->buttons.add(de() ? "Abbrechen" : "Cancel"); ret answer; } sS initialMessage() { print("initialMessage, lang=" + session->language()); ret or2(answer("#greeting"), "Hello"); } sO dbBot() { ret getBot(dbBotID); } sbool de() { ret eqic(session->language(), "de"); } sbool botOn() { ret isTrue(call(dbBot(), "botOn")); } sbool botAutoOpen() { ret isTrue(call(dbBot(), "botAutoOpen")); } sS processURLInInput(S s) { LS urls = regexpAllMatches(urlFinderRegexp(), s); if (empty(urls)) null; ret "URLs found:\n" + lines(urls); }