Libraryless. Click here for Pure Java version (4925L/33K/113K).
1 | !752 |
2 | |
3 | static long minPostDelay = 1000; // 1 second per program |
4 | static long presenceTimeout = 1000*60; // 1 minute |
5 | static S defaultUser = "anon"; |
6 | static bool showAds; |
7 | |
8 | static S myURL; |
9 | static S image = "http://eyeocr.sourceforge.net/filestore/filestore.php?cmd=serve&file=blob_1003097&contentType=image/png"; |
10 | static S image2 = "http://eyeocr.sourceforge.net/filestore/filestore.php?cmd=serve&file=blob_1003101&contentType=image/png"; |
11 | |
12 | sclass Msg { |
13 | long timestamp; |
14 | S user, text; |
15 | long id; |
16 | |
17 | S programID; // which program posted this |
18 | |
19 | *() {} |
20 | *(long *timestamp, S *user, S *text) {} |
21 | } |
22 | |
23 | sclass Presence { |
24 | long timestamp; |
25 | |
26 | *() {} |
27 | *(long *timestamp) {} |
28 | } |
29 | |
30 | sclass Suggest { |
31 | long id, onMsgID; |
32 | S text, user, _if, byUser, ip; |
33 | bool likeNow; |
34 | } |
35 | |
36 | static PersistentLog<Msg> messages; |
37 | static new Map<S, Presence> presences; |
38 | static Map<S, L<S>> permanentPresences = new TreeMap; |
39 | static PersistentLog<Suggest> suggests; |
40 | static new MultiMap<Long, Suggest> suggestsByOnMsg; |
41 | static Q feeder; |
42 | static new L<S> listeners; // bot IDs |
43 | |
44 | p { |
45 | showAds = amIProgram("#1003081"); |
46 | myURL = rawBotLink(); |
47 | messages = new PersistentLog("messages"); |
48 | load("presences"); |
49 | load("permanentPresences"); |
50 | clearPresences(); |
51 | feeder = new Q("Simple Chat Room Feeder", true); |
52 | load("listeners"); |
53 | suggests = new PersistentLog("suggests"); |
54 | for (Suggest s : suggests) index(s); |
55 | } |
56 | |
57 | static void index(Suggest s) { |
58 | suggestsByOnMsg.put(s.onMsgID, s); |
59 | } |
60 | |
61 | ssynchronized bool clearPresences() { |
62 | long expire = now()-presenceTimeout; |
63 | bool anyChange = false; |
64 | for (S name : cloneList(keys(presences))) { |
65 | Presence p = presences.get(name); |
66 | |
67 | // presences expired or program gone away => drop presence |
68 | if (p.timestamp < expire) { |
69 | anyChange = true; |
70 | presences.remove(name); |
71 | } |
72 | } |
73 | if (anyChange) |
74 | save("presences"); |
75 | ret anyChange; |
76 | } |
77 | |
78 | ssynchronized bool clearPermanentPresences() { |
79 | bool anyChange = false; |
80 | for (S programID : cloneList(keys(permanentPresences))) { |
81 | if (getBot(programID) == null) { |
82 | anyChange = true; |
83 | permanentPresences.remove(programID); |
84 | } |
85 | } |
86 | if (anyChange) |
87 | save("permanentPresences"); |
88 | ret anyChange; |
89 | } |
90 | |
91 | ssynchronized S post(S user, S text) { |
92 | ret post(user, text, programID()); |
93 | } |
94 | |
95 | ssynchronized S post(S user, S text, S programID) { |
96 | addPresence(user); |
97 | Msg last = last(messages); |
98 | if (last != null) { |
99 | if (eq(last.text, text) && eq(last.user, user)) |
100 | ret "don't repost"; // don't re-post |
101 | if (!sameSnippetID(programID, programID()) |
102 | && sameSnippetID(last.programID, programID) |
103 | && last.timestamp >= now()-minPostDelay) |
104 | ret "don't hammer"; // anti hammering |
105 | } |
106 | Msg msg = new Msg(now(), user, text); |
107 | msg.programID = programID; |
108 | msg.id = l(messages)+1; |
109 | messages.add(msg); |
110 | feeder.add(runnable { feedThem(); }); |
111 | ret "ok"; |
112 | } |
113 | |
114 | ssynchronized int size() { |
115 | ret l(messages); |
116 | } |
117 | |
118 | ssynchronized L<Msg> getMessages(int startIndex, int endIndex) { |
119 | ret newSubList(messages, startIndex, endIndex); |
120 | } |
121 | |
122 | ssynchronized S html(S uri, Map<S, S> params) { |
123 | registerVisitor(); |
124 | S user = or2(params.get("user"), defaultUser); |
125 | if (params.get("user") != null) { |
126 | setCookie("chatuser", user); |
127 | //print("Setting user cookie: " + "chatuser" + " = " + user); |
128 | } else { |
129 | user = or2(getCookie("chatuser"), defaultUser); |
130 | //print("Read user cookie: " + user); |
131 | } |
132 | S text = params.get("text"); |
133 | bool all = eq("1", params.get("all")); |
134 | bool reveal = false; // eq("1", params.get("reveal")); |
135 | bool ad = eq("1", params.get("ad")); |
136 | if (params.get("ad") != null) |
137 | setCookie("chatad", ad ? "1" : "0"); |
138 | else |
139 | ad = eq("1", getCookie("chatad")); |
140 | int lines = parseInt(or2(params.get("n"), "15")); |
141 | //printAllCookies(); |
142 | |
143 | addPresence(user); |
144 | |
145 | if (eq(uri, "/n")) |
146 | ret str(l(messages)); |
147 | |
148 | bool robo = eq("1", params.get("robo")); |
149 | |
150 | if (eq(uri, "/incremental")) { |
151 | int a = parseInt(params.get("a")); |
152 | int b = parseInt(params.get("b")); |
153 | if (b == 0) b = l(messages); |
154 | L msgs = subList(messages, a, b); |
155 | if (robo) |
156 | ret htmlencode(structure(msgs)); |
157 | if (empty(msgs)) ret ""; |
158 | ret "<!-- " + l(messages) + "-->\n" |
159 | + renderMessages(msgs, reveal, false, ad); |
160 | } |
161 | |
162 | if (eq(uri, "/presences")) |
163 | ret htmlencode(structure(allPresences())); |
164 | |
165 | S suggest = params.get("suggest"); |
166 | if (nempty(suggest)) { |
167 | new Suggest s; |
168 | s.text = suggest; |
169 | s.user = params.get("user"); |
170 | s._if = params.get("if"); |
171 | s.onMsgID = parseLong(params.get("id")); |
172 | s.id = l(suggests)+1; |
173 | s.byUser = user; |
174 | s.likeNow = params.get("likenow") != null; |
175 | suggests.add(s); |
176 | index(s); |
177 | ret hrefresh(rawLink()); |
178 | } |
179 | |
180 | if (nempty(text)) { |
181 | post(user, text); |
182 | ret robo ? "ok" : hrefresh(rawLink()); |
183 | } |
184 | |
185 | if (robo) ret "ok"; |
186 | |
187 | S formContents = ""; |
188 | |
189 | formContents += "Your nick: " + htag("input", "", "type", "text", "name", "user", "value", user); |
190 | formContents += " Your line: " + htag("input", "", "type", "text", "name", "text", "value", "", "autofocus", "1"); |
191 | formContents += " " + htag("input", "", "type", "submit", "value", "Send"); |
192 | //formContents += hiddenFields(params, keepFields); |
193 | |
194 | L msgs = messages; |
195 | if (!all) msgs = takeLast(msgs, lines); |
196 | |
197 | ret /*h3("Computer Chat Room - where nobody knows you're a \"bot\"")*/ |
198 | mobileFontFixer() |
199 | + loadJQuery() |
200 | + javaScript() |
201 | + htitle("RoboChat") |
202 | + body( |
203 | p(ahref(myURL, himg(image)) + " " + ahref(myURL, himg(image2))) |
204 | + div(renderMessages(msgs, reveal, l(messages) > l(msgs), ad), "id", "maindiv", "style", "height: 400px; overflow-x: scroll") |
205 | //+ htmlTable(objectToMap(msgs)) |
206 | + p("Present: " + structure(allPresences())) |
207 | + p(htag("form", formContents, "method", "POST", "action", myURL)) |
208 | + p(targetBlankLink("http://ai1.lol/wiki/", "Other Homepage") |
209 | + p(targetBlankLink("https://medium.com/@stefanreich", "Read Stefan on medium.com") + "<br>" + targetBlankLink(rawBotLink("#1002213"), "Talk to Eleu")) |
210 | + (showAds ? p(ads()) : ""), |
211 | "onLoad", [[ $("#maindiv").scrollTop(1E10); start(); ]]); |
212 | } |
213 | |
214 | ssynchronized void addPresence(S user) { |
215 | if (empty(user)) ret; |
216 | if (permanentPresences.containsKey(user)) ret; |
217 | Presence p = new Presence(now()); |
218 | presences.put(user, p); |
219 | save("presences"); |
220 | } |
221 | |
222 | ssynchronized S postForeign(Class mainClass, S programID, S user, S text) { |
223 | ret post(user, text, programID); |
224 | } |
225 | |
226 | ssynchronized void addListener(S botID) { |
227 | if (setAdd(listeners, formatSnippetID(botID))) { |
228 | print("Chat: Added listener " + botID + ", now: " + structure(listeners)); |
229 | save("listeners"); |
230 | } |
231 | } |
232 | |
233 | ssynchronized void removeListener(S botID) { |
234 | listeners.remove(formatSnippetID(botID)); |
235 | print("Chat: Removing listener " + botID + ", remaining: " + structure(listeners)); |
236 | save("listeners"); |
237 | } |
238 | |
239 | ssynchronized L<S> getListeners() { |
240 | ret cloneList(listeners); |
241 | } |
242 | |
243 | static void feedThem() { |
244 | L<S> listeners = getListeners(); |
245 | print("Feeding: " + structure(listeners)); |
246 | for (S botID : listeners) pcall { |
247 | O bot = getBot(botID); |
248 | if (bot != null) |
249 | callOpt(bot, "simpleChatUpdated"); |
250 | else |
251 | removeListener(botID); |
252 | } |
253 | } |
254 | |
255 | static S renderMessages(L<Msg> msgs, bool reveal, bool moreAbove, |
256 | bool ad) { |
257 | new StringBuilder buf; |
258 | if (moreAbove) |
259 | buf.append(ahref(rawLink() + "?all=1", "[...]") + "<br>\n"); |
260 | for (Msg m : msgs) { |
261 | buf.append(b(htmlencode(m.user) + ":") + " " + htmlencode(m.text)); |
262 | if (ad) { |
263 | S suggestLink = "#"; // rawLink("/suggest?id=" + m.id); |
264 | S formContents = "Suggest saying " + hinputfield("suggest") |
265 | + " as " + hinputfield("user", "someone") + " if (optional) " + hinputfield("if") + hhidden("id", m.id) + ", like now " |
266 | + hcheckbox("likenow", true) + " . " |
267 | + hsubmit("OK"); |
268 | S popupHtml = tag("form", formContents, "action", rawLink("suggest"), "method", "POST", "style", "display: inline-block;"); |
269 | S extra = "$('#extra" + m.id + "')"; |
270 | S onClick = extra + ".html(" + extra + ".html() == '' ? " + javascriptQuote(popupHtml) + " : ''); return false;"; |
271 | buf.append(" " + ahref(suggestLink, ">", "onClick", onClick)); |
272 | L<Suggest> suggests = suggestsByOnMsg.get(m.id); |
273 | if (nempty(suggests)) |
274 | buf.append(" [" + n(l(suggests), "suggest") + "]"); |
275 | } |
276 | /*buf.append(" " + tag("div", "", "id", "extra" + m.id, |
277 | "style", "display: inline"));*/ |
278 | buf.append(" " + tag("span", "", "id", "extra" + m.id)); |
279 | buf.append("<br>\n"); |
280 | } |
281 | ret str(buf); |
282 | } |
283 | |
284 | static S mobileFontFixer() { |
285 | ret [[<meta name="viewport" content="width=device-width, initial-scale=1">]]; |
286 | } |
287 | |
288 | // called from other bots - sets a permanent presence |
289 | ssynchronized void setPermanentPresence(S programID, L<S> names) { |
290 | permanentPresences.put(formatSnippetID(programID), names); |
291 | clearPermanentPresences(); |
292 | save("permanentPresences"); |
293 | } |
294 | |
295 | ssynchronized L<S> allPresences() { |
296 | clearPresences(); |
297 | clearPermanentPresences(); |
298 | ret asList(joinSets( |
299 | asSet(concatLists(values(permanentPresences))), |
300 | asSet(keys(presences)))); |
301 | } |
302 | |
303 | static S ads() { |
304 | ret [[<a href="//www.interserver.net/r/251595"><img src="https://www.interserver.net/logos/interserver125by125.gif" alt="InterServer Web Hosting and VPS"></a>]]; |
305 | } |
306 | |
307 | static S javaScript() { |
308 | S link = rawLink("incremental?a="); |
309 | ret hjavascript([[ |
310 | var n = $N; |
311 | function start() { |
312 | $.get(LINK + n, function(src) { |
313 | var match = src.match(/\d+/); |
314 | if (match != null) { |
315 | n = parseInt(match[0]); |
316 | $("#maindiv").append(src); |
317 | $("#maindiv").scrollTop(1E10); |
318 | } |
319 | }, 'text'); |
320 | setTimeout(start, 10000); |
321 | } |
322 | ]]).replace("LINK", jsQuote(link)).replace("$N", lstr(messages)); |
323 | } |
download show line numbers debug dex old transpilations
Travelled to 17 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, jtubtzbbkimh, lhdilzshxjzv, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt, vzpxdoxhqfnw
No comments. add comment
Snippet ID: | #1003081 |
Snippet name: | RoboChat (Simple Chat Room) |
Eternal ID of this version: | #1003081/3 |
Text MD5: | 63564fd0dfb66484d73bd078211871b8 |
Transpilation MD5: | b0f79a08f73f6fd78d67ad76fd285a8b |
Author: | stefan |
Category: | javax |
Type: | JavaX source code |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2017-06-22 11:56:01 |
Source code size: | 10027 bytes / 323 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 1633 / 2969 |
Version history: | 2 change(s) |
Referenced in: | [show references] |