!7 sS dbBotID = #1026409; 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; bool allowFreeText; // only matters when there are buttons S value; // called before data entry void update(Runnable onChange) {} // called after data entry // return error message or null // call session->cancelForm(); to cancel the form (and make sure to return a text) S verifyData(S s) { null; } } 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"; } } sclass MainForm > FormInFlight { FormStep stepInterested, stepMailAddress, stepDevice, stepConfirm; *() { hashTag = "#mainForm"; steps.add(stepInterested = setAll(new StepInterested, key := "interested", desc := "Interested in free trial", displayText := "Are you after a free trial of our IPTV services?", buttons := ll("Yes, please!", "No, thanks."), allowFreeText := true)); steps.add(stepMailAddress = setAll(new StepMail, key := "mailAddress", desc := "E-mail address", displayText := "Please provide your e-mail address so we can contact you!", placeholder := "What is your e-mail address?",)); steps.add(stepDevice = nu FormStep( key := "device", desc := "Device", displayText := "Which IPTV-capable device do you own?", buttons := ll("Android box", "Android phone", "Android tablet", "Firestick", "Smart TV", "None/Unsure"), allowFreeText := true)); steps.add(stepConfirm = setAll(new StepConfirm, key := "confirm", placeholder := "", buttons := ll("Yes") )); } class StepInterested extends FormStep { S verifyData(S s) { s = trim(s); if (isNo(s) || matchStart("not", s)) { session->cancelForm(); ret answer("#notInterestedInTrial"); } if (!isYes(s)) { // cancel form, interpret as normal input session->cancelForm(); ret answer(s); } null; } } class StepMail extends FormStep { @Override S verifyData(S s) { ret callPrint1x isValidEmailAddress_simple('isValidEmailAddress_simple, trim(s)) ? null : "Please enter a valid email address."; } } class StepConfirm extends FormStep { void update(Runnable onChange) { if (set_trueIfChanged(this, displayText := summary() + "\n\n" + "Submit contact request?")) callF(onChange); } } S summary() { print("Will send to: " + sendToViberIDs()); ret mapToLines_rtrim(listMinus(steps, stepConfirm), step -> (nempty(step.desc) ? addSuffix(step.desc, ":") : step.displayText) + " " + step.value); } LS sendToViberIDs() { ret llNempties(loadTextFileTrim(javaxSecretDir("iptv-viber-id.txt")), stefanViberID()); } S complete() { LS viberIDs = sendToViberIDs(); S text = joinWithEmptyLines( "Contact request over smart-iptv-solutions.co.uk (" + timeInTimeZoneWithDate_24(ukTimeZone_string()) + ")", summary()); pcall { for (S viberID : sendToViberIDs()) viber_sendMessage(mandatoryViberToken(), /*"BotCompany"*/"smart-iptv-solutions.co.uk", viberID, text); ret "Thank you, your contact request was sent!"; } ret "Sorry, an error occurred sending the message."; } S cancel() { ret "Contact request cancelled"; } } 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 (!step.allowFreeText && nempty(step.buttons) && !cic(step.buttons, s)) ret deliverAnswerAndFormStep(de() ? "Bitte wählen Sie eine Option!" : "Please choose an option."); print("Verifying data " + quote(s) + " in step " + step); S error = step.verifyData(s); if (error != null) ret deliverAnswerAndFormStep(error); 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"; } } // 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) { if (session->form != null) session->form.update(r { session->change() }); /*if (form.shouldCancel()) { session->cancelForm(); ret answer; } */ FormInFlight form = session->form; // form may have cancelled itself in update if (form == null || 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")); }