Warning: session_start(): open(/var/lib/php/sessions/sess_6eafvm83hgd8ekfqm383k22e5l, O_RDWR) failed: No space left on device (28) in /var/www/tb-usercake/models/config.php on line 51
Warning: session_start(): Failed to read session data: files (path: /var/lib/php/sessions) in /var/www/tb-usercake/models/config.php on line 51
sO thoughtBot;
static int longPollTick = 100;
static int longPollMaxWait = 1000*60;
sS defaultUserName = "You";
sS botImageID = #1102802;
sS userImageID = #1102803;
sS baseLink;
sbool botOnRight = true;
static Set specialButtons = litciset("Cancel", "Back");
sclass Out extends DynamicObject {
S buttonsIntro;
LS buttons;
S placeholder;
S defaultInput;
}
sclass Msg extends DynamicObject {
long time;
bool fromUser;
S text;
Out out;
*() {}
*(bool *fromUser, S *text) { time = now(); }
}
concept Conversation {
bool authed;
S cookie;
new LL oldDialogs;
new L msgs;
void add(Msg m) {
syncAdd(msgs, m);
change();
}
int allCount() { ret lengthLevel2(oldDialogs) + l(msgs); }
int archiveSize() { ret lengthLevel2(oldDialogs); }
}
svoid pWebChatBot {
db();
thoughtBot = runDependent(thoughtBotID);
Class envType = fieldType(thoughtBot, "env");
if (envType != null)
setOpt(thoughtBot, "env", proxy(envType, (O) mc()));
}
static void sayAsync(S session, S text) {
lock dbLock();
Conversation conv = getConv(session);
conv.add(new Msg(false, text));
print("sayAsync " + session + ", new size=" + conv.allCount());
}
html {
temp tempRegisterThread();
S cookie = params.get('cookie);
if (empty(cookie)) {
registerVisitor();
cookie = cookieSent();
}
Conversation conv = nempty(cookie) ? getConv(cookie) : null;
print("URI: " + uri + ", cookie: " + cookie + ", msgs: " + l(conv.msgs));
S pw = trim(params.get('pw));
if (nempty(pw)) {
S realPW = trim(loadSecretTextFile("password.txt"));
if (empty(realPW)) ret errorMsg("Administrator has not set a password");
if (neq(pw, realPW))
ret errorMsg("Bad password, please try again");
cset(conv, authed := true);
if (nempty(params.get('redirect)))
ret hrefresh(params.get('redirect));
}
if (eq(uri, "/stats")) {
if (!conv.authed) ret serveAuthForm(rawLink(uri));
ret "Threads: " + ul_htmlEncode(getThreadNames(registeredThreads()));
}
if (eq(uri, "/logs")) {
if (!conv.authed) ret serveAuthForm(rawLink(uri));
ret webChatBotLogsHTML2(rawLink(uri), params);
}
if (eq(uri, "/auth-only")) {
if (eq(params.get('logout), "1"))
cset(conv, authed := false);
if (!conv.authed) ret serveAuthForm(params.get('uri));
ret "";
}
{
lock dbLock();
S message = trim(params.get("btn"));
if (empty(message)) message = trim(params.get("message"));
if (match("new dialog", message)) {
conv.oldDialogs.add(conv.msgs);
cset(conv, msgs := new L);
conv.change();
callOpt(thoughtBot, "clearSession", conv.cookie);
message = null;
}
call(thoughtBot, "setSession", cookie, params);
if (empty(conv.msgs))
conv.add(new Msg(false, initialMessage()));
if (nempty(message) && !lastUserMessageWas(conv, message)) {
print("Adding message: " + message);
conv.add(new Msg(true, message));
}
if (nempty(conv.msgs) && last(conv.msgs).fromUser) {
S reply = "";
Out out = null;
pcall {
reply = makeReply(last(conv.msgs).text);
out = (Out) quickImport(getThreadLocal(thoughtBot, "out"));
}
Msg msg = new Msg(false, reply);
msg.out = out;
conv.add(msg);
}
} // locked
if (eq(uri, "/msg")) ret withHeader("OK");
if (eq(uri, "/incremental")) {
int a = parseInt(params.get("a"));
long start = sysNow();
L msgs;
bool first = true;
while (licensed() && sysNow() < start+longPollMaxWait) {
int as = conv.archiveSize();
msgs = cloneSubList(conv.msgs, a-as);
bool newDialog = a <= as;
if (empty(msgs)) {
if (first) {
print("Long poll starting on " + cookie + ", " + a + "/" + a);
first = false;
}
sleep(longPollTick);
} else {
if (first) print("Long poll ended.");
new StringBuilder buf;
renderMessages(buf, msgs);
ret withHeader("\n" + buf);
}
}
ret withHeader("");
}
{
lock dbLock();
processParams(params);
S html = loadSnippet(templateID); // TODO: cache
new StringBuilder buf;
// incremental only
//renderMessages(buf, conv.msgs);
S langlinks = "";
if (html.contains(langlinks))
html = html.replace(langlinks,
ahref(rawLink("eng"), "English") + " | " + ahref(rawLink("deu"), "German"));
html = html.replace("#BOTIMG#", imageSnippetURL(#1102802));
html = html.replace("#N#", "0" /*str(conv.allCount())*/);
html = html.replace("#INCREMENTALURL#", baseLink + "/incremental?a=");
html = html.replace("#MSGURL#", baseLink + "/msg?message=");
if (nempty(params.get("debug")))
html = html.replace("var showActions = false;", "var showActions = true;");
html = html.replace("$HEADING", heading);
html = html.replace("", str(buf));
html = hreplaceTitle(html, heading);
ret withHeader(subBot_serveJavaScript(html));
}
}
sO withHeader(S html) {
ret withHeader(subBot_serveHTML(html));
}
sO withHeader(O response) {
call(response, 'addHeader, "Access-Control-Allow-Origin", "*");
ret response;
}
svoid renderMessages(StringBuilder buf, L msgs) {
new Set buttonsToSkip;
new LS buttonsHtml;
for (Msg m : msgs) {
if (!m.fromUser && eq(m.text, "-")) continue;
S html = htmlEncode2_nlToBr(trim(m.text));
// pull back & cancel buttons to beginning of msg
if (m == last(msgs) && m.out != null) {
fOr (S btn : m.out.buttons)
if (specialButtons.contains(btn)) {
buttonsToSkip.add(btn);
buttonsHtml.add(renderButtons(ll(btn)));
}
}
if (nempty(buttonsHtml) && l(m.out.buttons) == l(buttonsToSkip))
html += " " + hspan(" ", class := "chat-button-span") + lines(buttonsHtml);
else
buttonsToSkip.clear();
appendMsg(buf, m.fromUser ? defaultUserName : botName, formatTime(m.time), html, !m.fromUser);
}
appendButtons(buf, last(msgs).out, buttonsToSkip);
}
svoid appendMsg(StringBuilder buf, S name, S time, S text, bool bot) {
S imgLink = snippetImgLink(bot ? botImageID : userImageID);
// img now before text because of position: absolute
buf.append(([[
$NAME
$TEXT
]]).replace("$LR", bot != botOnRight ? "left" : "right")
.replace("$IMG", imgLink)
.replace("$NAME", name)
.replace("$TIME", time)
.replace("$TEXT", text));
}
sS replaceButtonText(S s) {
if (eqic(s, "back")) ret unicode_undoArrow();
if (eqic(s, "cancel")) ret unicode_crossProduct();
ret s;
}
sS renderButtons(LS buttons) {
new LS out;
for i over buttons: {
S code = buttons.get(i);
S text = replaceButtonText(code);
out.add(hbuttonOnClick_returnFalse(text, "submitAMsg(" + jsQuote(text) + ")", class := "chatbot-choice-button", title := eq(code, text) ? null : code));
if (!specialButtons.contains(code)
&& i+1 < l(buttons) && specialButtons.contains(buttons.get(i+1)))
out.add(" ");
}
ret lines(out);
}
svoid appendButtons(StringBuilder buf, Out out, Set buttonsToSkip) {
S placeholder = out == null ? "" : unnull(out.placeholder);
S defaultInput = out == null ? "" : unnull(out.defaultInput);
buf.append(hscript("chatBot_setInput(" + jsQuote(defaultInput) + ", " + jsQuote(placeholder) + ");"));
if (out == null) ret;
LS buttons = listMinusSet(out.buttons, buttonsToSkip);
if (empty(buttons)) ret;
S buttonsHtml = renderButtons(buttons);
buf.append([[
]].replace("$BUTTONS", htmlEncode2_nlToBr(appendNewLineIfNempty(trim(out.buttonsIntro))) + buttonsHtml));
}
svoid appendDate(StringBuilder buf, S date) {
buf.append([[
]].replace("DATE", date));
}
sS initialMessage() {
ret (S) call(thoughtBot, "initialMessage");
}
static bool lastUserMessageWas(Conversation conv, S message) {
Msg m = last(conv.msgs);
ret m != null && m.fromUser && eq(m.text, message);
}
sS makeReply(S message) {
ret callStaticAnswerMethod(thoughtBot, message);
}
sS formatTime(long time) {
ret timeInTimeZoneWithOptionalDate_24(germanTimeZone_string(), time);
}
sS formatDialog(S id, L msgs) {
new L lc;
for (Msg m : msgs)
lc.add(htmlencode((m.fromUser ? "> " : "< ") + m.text));
ret id + ul(lc);
}
static Conversation getConv(fS cookie) {
ret withDBLock(func -> Conversation {
uniq(Conversation, +cookie)
});
}
sS serveAuthForm(S redirect) {
ret hhtml(hhead(htitle("Authorization required")) + hbody(hfullcenter(
h3_htmlEncode(heading + " Admin")
+ hpostform(
hhidden(+redirect)
+ "Password: " + hpassword('pw) + "
" + hsubmit()))));
}
sS errorMsg(S msg) {
ret hhtml(hhead_title("Error") + hbody(hfullcenter(msg + "
" + ahref(jsBackLink(), "Back"))));
}