Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

374
LINES

< > BotCompany Repo | #1026250 // Web Chat Bot Include v3 [used by IPTV]

JavaX fragment (include)

1  
sO thoughtBot;
2  
3  
static int longPollTick = 100;
4  
static int longPollMaxWait = 1000*30; // lowered to 30 seconds
5  
6  
sS botImageID = #1102802;
7  
sS userImageID = #1102803;
8  
sS chatHeaderImageID = #1102802;
9  
sS cssID = #1026220;
10  
sS baseLink;
11  
sbool botOnRight = true;
12  
sS timeZone = germanTimeZone_string();
13  
14  
static Set<S> specialButtons = litciset("Cancel", "Back", "Abbrechen", "Zurück");
15  
16  
sclass Out extends DynamicObject {
17  
  S buttonsIntro;
18  
  LS buttons;
19  
  S placeholder;
20  
  S defaultInput;
21  
}
22  
23  
sclass Msg extends DynamicObject {
24  
  long time;
25  
  bool fromUser;
26  
  S text;
27  
  Out out;
28  
  
29  
  *() {}
30  
  *(bool *fromUser, S *text) { time = now(); }
31  
}
32  
33  
concept Conversation {
34  
  bool authed;
35  
  S cookie, ip;
36  
  new LL<Msg> oldDialogs;
37  
  new L<Msg> msgs;
38  
39  
  void add(Msg m) {
40  
    syncAdd(msgs, m);
41  
    change();
42  
    vmBus_send chatBot_messageAdded(mc(), this, m);
43  
  }
44  
  
45  
  int allCount() { ret lengthLevel2(oldDialogs) + l(msgs); }
46  
  int archiveSize() { ret lengthLevel2(oldDialogs); }
47  
}
48  
49  
svoid pWebChatBot {
50  
  db();
51  
  if (thoughtBotID != null) thoughtBot = runDependent(thoughtBotID);
52  
  Class envType = fieldType(thoughtBot, "env");
53  
  if (envType != null)
54  
    setOpt(thoughtBot, "env", proxy(envType, (O) mc()));
55  
}
56  
57  
static void sayAsync(S session, S text) {
58  
  lock dbLock();
59  
  Conversation conv = getConv(session);
60  
  conv.add(new Msg(false, text));
61  
  print("sayAsync " + session + ", new size=" + conv.allCount());
62  
}
63  
64  
html {
65  
  temp tempRegisterThread();
66  
  S cookie = params.get('cookie);
67  
  if (empty(cookie)) {
68  
    registerVisitor();
69  
    cookie = cookieSent();
70  
  }
71  
  Conversation conv = nempty(cookie) ? getConv(cookie) : null;
72  
  if (conv != null)
73  
    cset(conv, ip := subBot_clientIP());
74  
  print("URI: " + uri + ", cookie: " + cookie + ", msgs: " + l(conv.msgs));
75  
  
76  
  S pw = trim(params.get('pw));
77  
  if (nempty(pw)) {
78  
    S realPW = trim(loadSecretTextFile("password.txt"));
79  
    if (empty(realPW)) ret errorMsg("Administrator has not set a password");
80  
    if (neq(pw, realPW))
81  
      ret errorMsg("Bad password, please try again");
82  
    cset(conv, authed := true);
83  
    if (nempty(params.get('redirect)))
84  
      ret hrefresh(params.get('redirect));
85  
  }
86  
  
87  
  if (eq(uri, "/stats")) {
88  
    if (!conv.authed) ret serveAuthForm(rawLink(uri));
89  
    ret "Threads: " + ul_htmlEncode(getThreadNames(registeredThreads()));
90  
  }
91  
      
92  
  if (eq(uri, "/logs")) {
93  
    if (!conv.authed) ret serveAuthForm(rawLink(uri));
94  
    ret webChatBotLogsHTML2(rawLink(uri), params);
95  
  }
96  
97  
  if (eq(uri, "/auth-only")) {
98  
    if (eq(params.get('logout), "1"))
99  
      cset(conv, authed := false);
100  
    if (!conv.authed) ret serveAuthForm(params.get('uri));
101  
    ret "";
102  
  }
103  
104  
  {
105  
    lock dbLock();
106  
    
107  
    S message = trim(params.get("btn"));
108  
    if (empty(message)) message = trim(params.get("message"));
109  
    
110  
    if (match("new dialog", message)) {
111  
      conv.oldDialogs.add(conv.msgs);
112  
      cset(conv, msgs := new L);
113  
      conv.change();
114  
      callOpt(thoughtBot, "clearSession", conv.cookie);
115  
      vmBus_send chatBot_clearedSession(mc(), conv);
116  
      message = null;
117  
    }
118  
    
119  
    call(thoughtBot, "setSession", cookie, params);
120  
    
121  
    if (empty(conv.msgs))
122  
      addReplyToConvo(conv, lambda0 initialMessage);
123  
      
124  
    if (nempty(message) && !lastUserMessageWas(conv, message)) {
125  
      print("Adding message: " + message);
126  
      conv.add(new Msg(true, message));
127  
    }
128  
  
129  
    if (nempty(conv.msgs) && last(conv.msgs).fromUser)
130  
      addReplyToConvo(conv, () -> makeReply(last(conv.msgs).text));
131  
  } // locked
132  
  
133  
  if (eq(uri, "/msg")) ret withHeader("OK");
134  
  
135  
  if (eq(uri, "/incremental")) {
136  
    vmBus_send chatBot_userPolling(mc(), conv);
137  
    int a = parseInt(params.get("a"));
138  
139  
    long start = sysNow();
140  
    L msgs;
141  
    bool first = true;
142  
    while (licensed() && sysNow() < start+longPollMaxWait) {
143  
      int as = conv.archiveSize();
144  
      msgs = cloneSubList(conv.msgs, a-as);
145  
      bool newDialog = a <= as;
146  
      if (empty(msgs)) {
147  
        if (first) {
148  
          print("Long poll starting on " + cookie + ", " + a + "/" + a);
149  
          first = false;
150  
        }
151  
        sleep(longPollTick);
152  
      } else {
153  
        if (first) print("Long poll ended.");
154  
        new StringBuilder buf;
155  
        renderMessages(buf, msgs);
156  
        ret withHeader("<!-- " + conv.allCount() + " " + (newDialog ? "NEW DIALOG " : "") + "-->\n"  + buf);
157  
      }
158  
    }
159  
    ret withHeader("");
160  
  }
161  
  
162  
  {
163  
    lock dbLock();
164  
    processParams(params);
165  
    S html = loadSnippet(templateID); // TODO: cache
166  
    new StringBuilder buf;
167  
168  
    // incremental only    
169  
    //renderMessages(buf, conv.msgs);
170  
  
171  
    S langlinks = "<!-- langlinks here -->";
172  
    if (html.contains(langlinks))
173  
      html = html.replace(langlinks,
174  
        ahref(rawLink("eng"), "English") + " | " + ahref(rawLink("deu"), "German"));
175  
    
176  
    html = html.replace("#BOTIMG#", imageSnippetURLOrEmptyGIF(chatHeaderImageID));
177  
    html = html.replace("#N#", "0" /*str(conv.allCount())*/);
178  
    html = html.replace("#INCREMENTALURL#", baseLink + "/incremental?a=");
179  
    html = html.replace("#MSGURL#", baseLink + "/msg?message=");
180  
    html = html.replace("#CSS_ID#", psI_str(cssID));
181  
    if (nempty(params.get("debug")))
182  
      html = html.replace("var showActions = false;", "var showActions = true;");
183  
  
184  
    html = html.replace("#AUTOOPEN#", jsBool(botAutoOpen());
185  
    html = html.replace("#BOT_ON#", jsBool(botOn());
186  
    html = html.replace("$HEADING", heading);
187  
    html = html.replace("<!-- MSGS HERE -->", str(buf));
188  
    html = hreplaceTitle(html, heading);
189  
    
190  
    if (eqGet(params, "_botDemo", "1"))
191  
      ret hhtml(hhead(
192  
        htitle(heading)
193  
        + loadJQuery()
194  
      ) + hbody(hjavascript(html)));
195  
    else
196  
      ret withHeader(subBot_serveJavaScript(html));
197  
  }
198  
}
199  
200  
svoid addReplyToConvo(Conversation conv, IF0<S> think) {
201  
  S reply = "";
202  
  Out out = null;
203  
  pcall {
204  
    reply = think!;
205  
    out = (Out) quickImport(getThreadLocal(thoughtBot, "out"));
206  
  }
207  
  Msg msg = new Msg(false, reply);
208  
  msg.out = out;
209  
  conv.add(msg);
210  
}
211  
212  
sO withHeader(S html) {
213  
  ret withHeader(subBot_noCacheHeaders(subBot_serveHTML(html)));
214  
}
215  
216  
sO withHeader(O response) {
217  
  call(response, 'addHeader, "Access-Control-Allow-Origin", "*");
218  
  ret response;
219  
}
220  
221  
sS renderMessageText(S text) {
222  
  ret replace(htmlEncode2_nlToBr(trim(text)),
223  
    ":wave:", html_wavingHand());
224  
}
225  
226  
svoid renderMessages(StringBuilder buf, L<Msg> msgs) {
227  
  new Set<S> buttonsToSkip;
228  
  new LS buttonsHtml;
229  
  for (Msg m : msgs) {
230  
    if (!m.fromUser && eq(m.text, "-")) continue;
231  
    S html = renderMessageText(m.text);
232  
    // pull back & cancel buttons to beginning of msg
233  
    if (m == last(msgs) && m.out != null) {
234  
      fOr (S btn : m.out.buttons)
235  
        if (specialButtons.contains(btn)) {
236  
          buttonsToSkip.add(btn);
237  
          buttonsHtml.add(renderButtons(ll(btn)));
238  
        }
239  
    }
240  
    if (nempty(buttonsHtml) && l(m.out.buttons) == l(buttonsToSkip))
241  
      html += " " + hspan("&nbsp;&nbsp;", class := "chat-button-span") + lines(buttonsHtml);
242  
    else
243  
      buttonsToSkip.clear();
244  
    appendMsg(buf, m.fromUser ? defaultUserName() : botName, formatTime(m.time), html, !m.fromUser);
245  
  }
246  
      
247  
  appendButtons(buf, last(msgs).out, buttonsToSkip);
248  
}
249  
250  
svoid appendMsg(StringBuilder buf, S name, S time, S text, bool bot) {
251  
  // img now before text because of position: absolute
252  
  // removed from last div: <span class="direct-chat-timestamp pull-$LR">$TIME</span>
253  
  buf.append(([[<div class="direct-chat-msg doted-border">]]);
254  
  
255  
  S imgID = bot ? botImageID : userImageID;
256  
  if (nempty(imgID))
257  
    buf.append([[<img alt="message user image" src="$IMG" class="direct-chat-img">]]
258  
      .replace("$IMG", snippetImgLink(imgID)));
259  
  
260  
  buf.append([[
261  
    <div class="direct-chat-info clearfix">
262  
    <div style="float: right" class="direct-chat-timestamp">$TIME</div>
263  
    <span class="direct-chat-name pull-left">$NAME</span>
264  
    </div>
265  
    <div class="direct-chat-text pull-$LR clearfix">
266  
      $TEXT
267  
    </div>
268  
    <div class="direct-chat-info clearfix"></div>
269  
  </div>
270  
   ]].replace("$LR", bot != botOnRight ? "left" : "right")
271  
     .replace("$NAME", name)
272  
     .replace("$TIME", time)
273  
     .replace("$TEXT", text));
274  
}
275  
276  
sS replaceButtonText(S s) {
277  
  if (eqicOneOf(s, "back", "zurück")) ret unicode_undoArrow();
278  
  if (eqicOneOf(s, "cancel", "Abbrechen")) ret unicode_crossProduct();
279  
  ret s;
280  
}
281  
282  
sS renderButtons(LS buttons) {
283  
  new LS out;
284  
  for i over buttons: {
285  
    S code = buttons.get(i);
286  
    S text = replaceButtonText(code);
287  
    out.add(hbuttonOnClick_returnFalse(text, "submitAMsg(" + jsQuote(text) + ")", class := "chatbot-choice-button", title := eq(code, text) ? null : code));
288  
    if (!specialButtons.contains(code)
289  
      && i+1 < l(buttons) && specialButtons.contains(buttons.get(i+1)))
290  
      out.add("&nbsp;&nbsp;");
291  
  }
292  
  ret lines(out);
293  
}
294  
295  
svoid appendButtons(StringBuilder buf, Out out, Set<S> buttonsToSkip) {
296  
  S placeholder = out == null ? "" : unnull(out.placeholder);
297  
  S defaultInput = out == null ? "" : unnull(out.defaultInput);
298  
  buf.append(hscript("chatBot_setInput(" + jsQuote(defaultInput) + ", " + jsQuote(placeholder) + ");"));
299  
  if (out == null) ret;
300  
  LS buttons = listMinusSet(out.buttons, buttonsToSkip);
301  
  if (empty(buttons)) ret;
302  
  S buttonsHtml = renderButtons(buttons);
303  
  buf.append([[<div class="direct-chat-msg doted-border">
304  
    <div class="direct-chat-buttons">
305  
      $BUTTONS
306  
    </div>
307  
  </div>
308  
    ]].replace("$BUTTONS", htmlEncode2_nlToBr(appendNewLineIfNempty(trim(out.buttonsIntro))) + buttonsHtml));
309  
}
310  
311  
svoid appendDate(StringBuilder buf, S date) {
312  
  buf.append([[
313  
  <div class="chat-box-single-line">
314  
    <abbr class="timestamp">DATE</abbr>
315  
  </div>]].replace("DATE", date));
316  
}
317  
318  
sS initialMessage() {
319  
  ret (S) call(thoughtBot, "initialMessage"); 
320  
}
321  
322  
static bool lastUserMessageWas(Conversation conv, S message) {
323  
  Msg m = last(conv.msgs);
324  
  ret m != null && m.fromUser && eq(m.text, message);
325  
}
326  
327  
sS makeReply(S message) {
328  
  ret callStaticAnswerMethod(thoughtBot, message);
329  
}
330  
331  
sS formatTime(long time) {
332  
  ret timeInTimeZoneWithOptionalDate_24(timeZone, time);
333  
}
334  
335  
sS formatDialog(S id, L<Msg> msgs) {
336  
  new L<S> lc;
337  
  for (Msg m : msgs)
338  
    lc.add(htmlencode((m.fromUser ? "> " : "< ") + m.text));
339  
  ret id + ul(lc);
340  
}
341  
342  
static Conversation getConv(fS cookie) {
343  
  ret withDBLock(func -> Conversation {
344  
    uniq(Conversation, +cookie)
345  
  });
346  
}
347  
348  
sS serveAuthForm(S redirect) {
349  
  ret hhtml(hhead(htitle("Authorization required")) + hbody(hfullcenter(
350  
    h3_htmlEncode(heading + " Admin")
351  
    + hpostform(
352  
      hhidden(+redirect)
353  
    + "Password: " + hpassword('pw) + "<br><br>" + hsubmit()))));
354  
}
355  
356  
sS errorMsg(S msg) {
357  
  ret hhtml(hhead_title("Error") + hbody(hfullcenter(msg + "<br><br>" + ahref(jsBackLink(), "Back"))));
358  
}
359  
360  
sS defaultUserName() {
361  
  ret de() ? "Sie" : "You";
362  
}
363  
364  
sbool de() {
365  
  ret isTrue(callOpt(thoughtBot, "de"));
366  
}
367  
368  
sbool botOn() {
369  
  ret isTrue(callOpt(thoughtBot, "botOn"));
370  
}
371  
372  
sbool botAutoOpen() {
373  
  ret !isFalse(callOpt(thoughtBot, "botAutoOpen"));
374  
}

Author comment

Began life as a copy of #1025984

download  show line numbers  debug dex  old transpilations   

Travelled to 7 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv

No comments. add comment

Snippet ID: #1026250
Snippet name: Web Chat Bot Include v3 [used by IPTV]
Eternal ID of this version: #1026250/12
Text MD5: 8b8f9486044742109014360fcbc7dce6
Author: stefan
Category: javax / a.i.
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2020-01-12 00:50:36
Source code size: 11263 bytes / 374 lines
Pitched / IR pitched: No / No
Views / Downloads: 152 / 402
Version history: 11 change(s)
Referenced in: [show references]