1 | class WorkerChat { |
2 | int workerLongPollTick = 200; |
3 | int workerLongPollMaxWait = 1000*30; |
4 | long lastWorkerRequested; // timestamp for notification sound |
5 | S mainAdminLink = "/"; |
6 | |
7 | O html(DynNewBot2.Req req) null { |
8 | SS params = req.params; |
9 | S uri = req.uri, uri2 = appendSlash(uri); |
10 | bool requestAuthed = req.auth != null; |
11 | Conversation conv = req.conv; |
12 | |
13 | if (startsWith(uri2, "/worker/")) { |
14 | if (!req.webRequest.isHttps()) |
15 | ret subBot_serveRedirect("https://" + req.webRequest.domain() + req.uri + htmlQuery(req.params)); |
16 | |
17 | if (!requestAuthed) ret serveAuthForm(params.get('uri)); |
18 | |
19 | if (nempty(params.get("turnBotOn"))) |
20 | conv.turnBotOn(); |
21 | |
22 | ret serveWorkerPage(req); |
23 | } |
24 | } |
25 | |
26 | // auth is tested before we get here |
27 | S serveWorkerPage(DynNewBot2.Req req) { |
28 | AuthedDialogID auth = req.auth; |
29 | Conversation conv = req.conv; |
30 | S uri = req.uri; |
31 | SS params = req.params; |
32 | S cookie = conv.cookie; |
33 | S uri2 = afterLastSlash(uri); |
34 | |
35 | if (eq(uri2, "availableWorkers")) |
36 | ret "Available workers: " + or2(joinWithComma(map(workersAvailable(), w -> w.renderAsHTML())), "-"); |
37 | |
38 | if (nempty(params.get('workerLogOut))) |
39 | cset(auth, loggedIn := null); |
40 | |
41 | if (auth.loggedIn != null && nempty(params.get('workerAvailableBox))) |
42 | if (cset_trueIfChanged(auth.loggedIn, available := nempty(params.get('workerAvailable)))) |
43 | noteConversationChange(); // update list of available workers |
44 | |
45 | if (nempty(params.get("acceptConversation"))) { |
46 | if (conv.worker == null) { // only if not accepted by anyone |
47 | cset(conv, worker := auth.loggedIn); |
48 | conv.turnBotOff(); |
49 | } |
50 | } |
51 | |
52 | S loginID = params.get('workerLogIn); |
53 | if (nempty(loginID)) |
54 | cset(auth, loggedIn := getConcept Worker(parseLong(loginID))); |
55 | |
56 | Map map = prependEmptyOptionForHSelect(mapToOrderedMap(conceptsSortedByFieldCI(Worker, 'loginName), |
57 | w -> pair(w.id, w.loginName))); |
58 | if (auth.loggedIn == null) |
59 | ret hsansserif() + p("You are not logged in as a worker") |
60 | + hpostform( |
61 | "Log in as: " + hselect("workerLogIn", map, conceptID(auth.loggedIn)) + " " + hsubmit("OK"), action := botMod().baseLink + "/worker"); |
62 | |
63 | // We are logged in |
64 | |
65 | if (eq(uri2, "conversation")) { |
66 | if (conv == null) ret "Conversation not found"; |
67 | // Serve the checkbox & the JavaScript |
68 | S onOffURL = botMod().baseLink + "/worker/botOnOff" + hquery(+cookie) + "&on="; |
69 | ret |
70 | hsansserif() + loadJQuery() |
71 | + hhidden(+cookie) // for other frame |
72 | + hpostform( |
73 | hhidden(+cookie) + |
74 | p(renderBotStatus(conv)) |
75 | + p(conv.botOn |
76 | ? hsubmit("Accept conversation", name := "acceptConversation") |
77 | : hsubmit("Turn bot back on", name := "turnBotOn")) |
78 | , action := botMod().baseLink + "/worker/innerFrameSet", target := "innerFrameSet") |
79 | |
80 | // include bot |
81 | + hscriptsrc(botMod().baseLink + "/script" + hquery(workerMode := 1, cookie := conv.cookie)); |
82 | } |
83 | |
84 | if (eq(uri2, "conversations")) { |
85 | cset(auth.loggedIn, lastOnline := now()); |
86 | bool poll = eq("1", params.get("poll")); // poll mode? |
87 | S content = ""; |
88 | |
89 | if (poll) { |
90 | long seenChange = parseLong(params.get("lastChange")); |
91 | vmBus_send chatBot_startingWorkerPoll(mc(), conv); |
92 | |
93 | long start = sysNow(); |
94 | L msgs; |
95 | bool first = true; |
96 | while (licensed() && sysNow() < start+workerLongPollMaxWait |
97 | && lastConversationChange == seenChange) |
98 | sleep(workerLongPollTick); |
99 | |
100 | printVars_str(+lastWorkerRequested, +seenChange); |
101 | if (lastWorkerRequested > seenChange) |
102 | content = hscript([[ |
103 | window.parent.parent.frames[0].sendDesktopNotification("A worker is requested!", { action: function() { window.focus(); } }); |
104 | window.parent.parent.frames[0].playWorkerRequestedSound(); |
105 | ]]); |
106 | |
107 | // if poll times out, send update anyway to update time calculations |
108 | } |
109 | |
110 | long pingThreshold = now()-activeConversationTimeout(); |
111 | L<Conversation> convos = sortByCalculatedFieldDesc(c -> c.lastMsgTime(), |
112 | conceptsWithFieldGreaterThan Conversation(lastPing := pingThreshold)); |
113 | |
114 | content += |
115 | hhiddenWithID(+lastConversationChange) + tag table( |
116 | hsimpletableheader("IP", "Country", "Bot/worker status", "Last change", "Last messages") |
117 | + mapToLines(convos, c -> { |
118 | L<Msg> lastMsgs = lastTwo(c.msgs); |
119 | S style = c == conv ? "background: #90EE90" : null; |
120 | S convLink = botMod().baseLink + "/worker/innerFrameSet" + hquery(cookie := c.cookie); |
121 | ret tag tr( |
122 | td(ahref(convLink, c.ip, target := "innerFrameSet")) + |
123 | td(getCountry(c)) + |
124 | td(renderBotStatus(c)) + |
125 | td(renderHowLongAgo(c.lastMsgTime())) + |
126 | td(ahref(convLink, hparagraphs(lambdaMap renderMsgForWorkerChat(lastMsgs)), target := "innerFrameSet", style := "text-decoration: none")), +style, /*class := "clickable-row", "data-href" := convLink*/); |
127 | }), class := "responstable"); |
128 | |
129 | if (poll) ret content; |
130 | |
131 | S incrementalURL = botMod().baseLink + "/worker/conversations?poll=1&lastChange="; |
132 | |
133 | ret hhtml( |
134 | hhead(hsansserif() + loadJQuery() |
135 | + hscript_clickableRows()) |
136 | + hbody(h3(botName) |
137 | + hpostform( |
138 | "Logged in as " |
139 | + htmlEncode2(auth.loggedIn.loginName) |
140 | + " (display name: " + htmlEncode2(auth.loggedIn.displayName) + ")" |
141 | + hhidden(workerAvailableBox := 1) |
142 | + " " |
143 | + hcheckboxWithText("workerAvailable", "I am available", auth.loggedIn.available, onclick := "form.submit()") |
144 | + " " |
145 | + hsubmit("Log out", name := "workerLogOut"), |
146 | target := "innerFrameSet", action := botMod().baseLink + "/worker/innerFrameSet") |
147 | + p("Available workers: " + b(or2(joinWithComma( |
148 | map(workersAvailable(), w -> w.displayName)), "none"))) |
149 | + h3("Active conversations") |
150 | + hcss_responstable() |
151 | + hdivWithID("contentArea", content) |
152 | + hscript([[ |
153 | function poll_start() { |
154 | var lastChange = $("#lastConversationChange").val(); |
155 | if (!lastChange) |
156 | setTimeout(poll_start, 1000); |
157 | else { |
158 | var url = "#INCREMENTALURL#" + lastChange; |
159 | console.log("Loading " + url); |
160 | $.get(url, function(src) { |
161 | if (src.match(/^ERROR/)) console.log(src); |
162 | else { |
163 | console.log("Loaded " + src.length + " chars"); |
164 | $("#contentArea").html(src); |
165 | } |
166 | setTimeout(poll_start, 1000); |
167 | }, 'text') |
168 | .fail(function() { |
169 | console.log("Rescheduling after fail"); |
170 | setTimeout(poll_start, 1000); |
171 | }); |
172 | } |
173 | } |
174 | poll_start(); |
175 | ]].replace("#INCREMENTALURL#", incrementalURL) |
176 | )); |
177 | } // end of worker/conversations part |
178 | |
179 | if (eq(uri2, "notificationArea")) |
180 | ret hhtml( |
181 | hhead(hsansserif() + loadJQuery()) |
182 | + hbody(hdesktopNotifications() |
183 | + div(small( |
184 | span(hbutton("CLICK HERE to enable notification sounds!"), id := "enableSoundsBtn") |
185 | + " | " |
186 | + span("", id := "notiStatus")), style := "float: right") |
187 | + hscript([[ |
188 | function enableSounds() { |
189 | document.removeEventListener('click', enableSounds); |
190 | $("#enableSoundsBtn").html("Notification sounds enabled"); |
191 | } |
192 | document.addEventListener('click', enableSounds); |
193 | |
194 | if (window.workerRequestedSound == null) { |
195 | console.log("Loading worker requested sound"); |
196 | window.workerRequestedSound = new Audio("https://botcompany.de/files/1400404/worker-requested.mp3"); |
197 | } |
198 | |
199 | function playWorkerRequestedSound() { |
200 | console.log("Playing worker requested sound"); |
201 | window.workerRequestedSound.play(); |
202 | } |
203 | window.playWorkerRequestedSound = playWorkerRequestedSound; |
204 | |
205 | ]]))); |
206 | |
207 | if (eq(uri2, "innerFrameSet")) |
208 | // serve frame set 2 |
209 | ret hhtml(hhead_title("Worker Chat [" + auth.loggedIn.loginName + "]") |
210 | + hframeset_cols("*,*", |
211 | tag frame("", name := "conversations", src := botMod().baseLink + "/worker/conversations" + hquery(+cookie)) + |
212 | tag frame("", name := "conversation", src := conv == null ? null : botMod().baseLink + "/worker/conversation" + hquery(+cookie)))); |
213 | |
214 | // serve frame set 1 |
215 | ret hhtml(hhead_title("Worker Chat [" + auth.loggedIn.loginName + "]") |
216 | + hframeset_rows("50,*", |
217 | tag frame("", name := "notificationArea", src := botMod().baseLink + "/worker/notificationArea") + |
218 | tag frame("", name := "innerFrameSet", src := conv == null ? null : botMod().baseLink + "/worker/innerFrameSet" + hquery(+cookie)))); |
219 | |
220 | } |
221 | |
222 | S renderMsgForWorkerChat(Msg msg) { |
223 | ret (msg.fromWorker != null ? htmlEncode2(msg.fromWorker.displayName) : msg.fromUser ? "User" : "Bot") + ": " + b(htmlEncode2If(shouldHtmlEncodeMsg(msg), msg.text)); |
224 | } |
225 | |
226 | Cl<Worker> workersAvailable() { |
227 | long timestamp = now()-workerLongPollMaxWait-10000; |
228 | ret filter(list(Worker), w -> w.available && w.lastOnline >= timestamp); |
229 | } |
230 | |
231 | bool anyWorkersAvailable() { |
232 | ret nempty(workersAvailable()); |
233 | } |
234 | |
235 | S renderBotStatus(Conversation conv) { |
236 | ret "Bot is " + b(conv.botOn ? "on" : "off") + "<br>" |
237 | + "Assigned worker: " + b(conv.worker == null ? "none" : conv.worker.displayName); |
238 | } |
239 | } |
Began life as a copy of #1028434
download show line numbers debug dex old transpilations
Travelled to 6 computer(s): bhatertpkbcr, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1029876 |
Snippet name: | WorkerChat, non-static (for DynNewBot2) |
Eternal ID of this version: | #1029876/14 |
Text MD5: | 7aadd45e473984a614b63241e323e2f5 |
Author: | stefan |
Category: | javax / web chat bots |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2020-09-30 21:37:10 |
Source code size: | 10069 bytes / 239 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 201 / 2934 |
Version history: | 13 change(s) |
Referenced in: | [show references] |