!752 static long minPostDelay = 1000; // 1 second per program static long presenceTimeout = 1000*60; // 1 minute static S defaultUser = "anon"; static bool showAds; static S myURL; static S image = "http://eyeocr.sourceforge.net/filestore/filestore.php?cmd=serve&file=blob_1003097&contentType=image/png"; static S image2 = "http://eyeocr.sourceforge.net/filestore/filestore.php?cmd=serve&file=blob_1003101&contentType=image/png"; sclass Msg { long timestamp; S user, text; long id; S programID; // which program posted this *() {} *(long *timestamp, S *user, S *text) {} } sclass Presence { long timestamp; *() {} *(long *timestamp) {} } sclass Suggest { long id, onMsgID; S text, user, _if, byUser, ip; bool likeNow; } static PersistentLog messages; static new Map presences; static Map> permanentPresences = new TreeMap; static PersistentLog suggests; static new MultiMap suggestsByOnMsg; static Q feeder; static new L listeners; // bot IDs p { showAds = amIProgram("#1003081"); myURL = rawBotLink(); messages = new PersistentLog("messages"); load("presences"); load("permanentPresences"); clearPresences(); feeder = new Q("Simple Chat Room Feeder", true); load("listeners"); suggests = new PersistentLog("suggests"); for (Suggest s : suggests) index(s); } static void index(Suggest s) { suggestsByOnMsg.put(s.onMsgID, s); } ssynchronized bool clearPresences() { long expire = now()-presenceTimeout; bool anyChange = false; for (S name : cloneList(keys(presences))) { Presence p = presences.get(name); // presences expired or program gone away => drop presence if (p.timestamp < expire) { anyChange = true; presences.remove(name); } } if (anyChange) save("presences"); ret anyChange; } ssynchronized bool clearPermanentPresences() { bool anyChange = false; for (S programID : cloneList(keys(permanentPresences))) { if (getBot(programID) == null) { anyChange = true; permanentPresences.remove(programID); } } if (anyChange) save("permanentPresences"); ret anyChange; } ssynchronized S post(S user, S text) { ret post(user, text, programID()); } ssynchronized S post(S user, S text, S programID) { addPresence(user); Msg last = last(messages); if (last != null) { if (eq(last.text, text) && eq(last.user, user)) ret "don't repost"; // don't re-post if (!sameSnippetID(programID, programID()) && sameSnippetID(last.programID, programID) && last.timestamp >= now()-minPostDelay) ret "don't hammer"; // anti hammering } Msg msg = new Msg(now(), user, text); msg.programID = programID; msg.id = l(messages)+1; messages.add(msg); feeder.add(runnable { feedThem(); }); ret "ok"; } ssynchronized int size() { ret l(messages); } ssynchronized L getMessages(int startIndex, int endIndex) { ret newSubList(messages, startIndex, endIndex); } ssynchronized S html(S uri, Map params) { registerVisitor(); S user = or2(params.get("user"), defaultUser); if (params.get("user") != null) { setCookie("chatuser", user); //print("Setting user cookie: " + "chatuser" + " = " + user); } else { user = or2(getCookie("chatuser"), defaultUser); //print("Read user cookie: " + user); } S text = params.get("text"); bool all = eq("1", params.get("all")); bool reveal = false; // eq("1", params.get("reveal")); bool ad = eq("1", params.get("ad")); if (params.get("ad") != null) setCookie("chatad", ad ? "1" : "0"); else ad = eq("1", getCookie("chatad")); int lines = parseInt(or2(params.get("n"), "15")); //printAllCookies(); addPresence(user); if (eq(uri, "/n")) ret str(l(messages)); bool robo = eq("1", params.get("robo")); if (eq(uri, "/incremental")) { int a = parseInt(params.get("a")); int b = parseInt(params.get("b")); if (b == 0) b = l(messages); L msgs = subList(messages, a, b); if (robo) ret htmlencode(structure(msgs)); if (empty(msgs)) ret ""; ret "\n" + renderMessages(msgs, reveal, false, ad); } if (eq(uri, "/presences")) ret htmlencode(structure(allPresences())); S suggest = params.get("suggest"); if (nempty(suggest)) { new Suggest s; s.text = suggest; s.user = params.get("user"); s._if = params.get("if"); s.onMsgID = parseLong(params.get("id")); s.id = l(suggests)+1; s.byUser = user; s.likeNow = params.get("likenow") != null; suggests.add(s); index(s); ret hrefresh(rawLink()); } if (nempty(text)) { post(user, text); ret robo ? "ok" : hrefresh(rawLink()); } if (robo) ret "ok"; S formContents = ""; formContents += "Your nick: " + htag("input", "", "type", "text", "name", "user", "value", user); formContents += " Your line: " + htag("input", "", "type", "text", "name", "text", "value", "", "autofocus", "1"); formContents += " " + htag("input", "", "type", "submit", "value", "Send"); //formContents += hiddenFields(params, keepFields); L msgs = messages; if (!all) msgs = takeLast(msgs, lines); ret /*h3("Computer Chat Room - where nobody knows you're a \"bot\"")*/ mobileFontFixer() + loadJQuery() + javaScript() + htitle("RoboChat") + body( p(ahref(myURL, himg(image)) + "   " + ahref(myURL, himg(image2))) + div(renderMessages(msgs, reveal, l(messages) > l(msgs), ad), "id", "maindiv", "style", "height: 400px; overflow-x: scroll") //+ htmlTable(objectToMap(msgs)) + p("Present: " + structure(allPresences())) + p(htag("form", formContents, "method", "POST", "action", myURL)) + p(targetBlankLink("http://ai1.lol/wiki/", "Other Homepage") + p(targetBlankLink("https://medium.com/@stefanreich", "Read Stefan on medium.com") + "
" + targetBlankLink(rawBotLink("#1002213"), "Talk to Eleu")) + (showAds ? p(ads()) : ""), "onLoad", [[ $("#maindiv").scrollTop(1E10); start(); ]]); } ssynchronized void addPresence(S user) { if (empty(user)) ret; if (permanentPresences.containsKey(user)) ret; Presence p = new Presence(now()); presences.put(user, p); save("presences"); } ssynchronized S postForeign(Class mainClass, S programID, S user, S text) { ret post(user, text, programID); } ssynchronized void addListener(S botID) { if (setAdd(listeners, formatSnippetID(botID))) { print("Chat: Added listener " + botID + ", now: " + structure(listeners)); save("listeners"); } } ssynchronized void removeListener(S botID) { listeners.remove(formatSnippetID(botID)); print("Chat: Removing listener " + botID + ", remaining: " + structure(listeners)); save("listeners"); } ssynchronized L getListeners() { ret cloneList(listeners); } static void feedThem() { L listeners = getListeners(); print("Feeding: " + structure(listeners)); for (S botID : listeners) pcall { O bot = getBot(botID); if (bot != null) callOpt(bot, "simpleChatUpdated"); else removeListener(botID); } } static S renderMessages(L msgs, bool reveal, bool moreAbove, bool ad) { new StringBuilder buf; if (moreAbove) buf.append(ahref(rawLink() + "?all=1", "[...]") + "
\n"); for (Msg m : msgs) { buf.append(b(htmlencode(m.user) + ":") + " " + htmlencode(m.text)); if (ad) { S suggestLink = "#"; // rawLink("/suggest?id=" + m.id); S formContents = "Suggest saying " + hinputfield("suggest") + " as " + hinputfield("user", "someone") + " if (optional) " + hinputfield("if") + hhidden("id", m.id) + ", like now " + hcheckbox("likenow", true) + " . " + hsubmit("OK"); S popupHtml = tag("form", formContents, "action", rawLink("suggest"), "method", "POST", "style", "display: inline-block;"); S extra = "$('#extra" + m.id + "')"; S onClick = extra + ".html(" + extra + ".html() == '' ? " + javascriptQuote(popupHtml) + " : ''); return false;"; buf.append(" " + ahref(suggestLink, ">", "onClick", onClick)); L suggests = suggestsByOnMsg.get(m.id); if (nempty(suggests)) buf.append(" [" + n(l(suggests), "suggest") + "]"); } /*buf.append(" " + tag("div", "", "id", "extra" + m.id, "style", "display: inline"));*/ buf.append(" " + tag("span", "", "id", "extra" + m.id)); buf.append("
\n"); } ret str(buf); } static S mobileFontFixer() { ret [[]]; } // called from other bots - sets a permanent presence ssynchronized void setPermanentPresence(S programID, L names) { permanentPresences.put(formatSnippetID(programID), names); clearPermanentPresences(); save("permanentPresences"); } ssynchronized L allPresences() { clearPresences(); clearPermanentPresences(); ret asList(joinSets( asSet(concatLists(values(permanentPresences))), asSet(keys(presences)))); } static S ads() { ret [[InterServer Web Hosting and VPS]]; } static S javaScript() { S link = rawLink("incremental?a="); ret hjavascript([[ var n = $N; function start() { $.get(LINK + n, function(src) { var match = src.match(/\d+/); if (match != null) { n = parseInt(match[0]); $("#maindiv").append(src); $("#maindiv").scrollTop(1E10); } }, 'text'); setTimeout(start, 10000); } ]]).replace("LINK", jsQuote(link)).replace("$N", lstr(messages)); }