set flag NoNanoHTTPD. static O stylesPost; // func(S) -> S sS botFont = "Righteous"; //"Inconsolata"; //"Pacifico"; sS botFontWeight = "normal"; sS botImageID = #1101140; //#1008323; sbool bottomRight = false; sS templateID = bottomRight ? #1008787 : /*#1009000*/#1011983; static F0 headerHTML; sS heading = "BotCompany | Stefan's Chat"; sbool forceHttps; sS roomName = "main"; // internal sS chatLineSymbol = "Chat line"; static int maxMessages = 40; static int visitors; static int messageLimit = stefansChat_messageLimit(); sbool postingDisabled = false; sbool showParses = true, notifications; static SS lastPosted = new ExpiringHashMap(2000); // ip -> message static volatile bool started; static O parserModule; static int longPollTick = 100; static int longPollMaxWait = 1000*60; static new L pagePostProcessors; concept WeirdIPs { Set ips = setInConcept(this, new TreeSet); } concept User { S ipAddress, cookie; } concept ByCookie { S cookie; S avatarID; } sclass Msg { S globalID = aGlobalIDUnlessLoading(); long time; User user; S text, parse; Bool goodParse; L buttons; bool botMark, auth; int nr; *() {} *(S ipAddress, S cookie, S *text) { user = uniq_sync(User, +ipAddress, +cookie); time = now(); } } concept Conversation { S cookie; // TODO: use synchro lists new LL oldDialogs; new L spam; new L msgs; int counter; void add(Msg m) { if (empty(oldDialogs)) oldDialogs.add(new L); if (l(msgs) >= maxMessages) listAdd(last(oldDialogs), popFirst(msgs)); listAdd(msgs, m); change(); pcall { processMsgCommands(m); } } // TODO /*void correctSanity() { if (empty(msgs)) ret; int nr = last(msgs).nr; int c = allCount(); while (nr > c) { msgs.add(Msg("null", "", "filler")); ++c; } while (nr > c) }*/ void moveToSpam(Msg m, S toID) { int i = msgs.indexOf(m); if (i < 0) ret; while (i < l(msgs) && neq(toID, msgs.get(i).globalID)) { spam.add(msgs.get(i)); msgs.remove(i); change(); if (empty(toID)) break; } moveMsgsUp(); } // move msgs from archive back to main dialog void moveMsgsUp { int delta = maxMessages-l(msgs); print("moveMsgsUp delta=" + delta); if (delta <= 0) ret; L old = last(oldDialogs); if (old == null) ret; L l = takeLast(delta, old); print("moveMsgsUp l=" + l(l)); if (empty(l)) ret; synchronized(msgs) { msgs.addAll(0, l); } removeLast(old, delta); change(); } int allCount() { ret archiveSize() + l(msgs); } int archiveSize() { ret lengthLevel2(oldDialogs) + l(spam); } } svoid stefansChat_init { loadPage_forcedTimeout = 20000; dbIndexing(ByCookie, 'cookie); started = true; for (Conversation conv) print("room=" + conv.cookie + ": allCount=" + conv.allCount() + " old=" + l(conv.oldDialogs)); L l = findConcepts(Conversation, cookie := roomName); if (l(l) > 1) { print("FIXING"); deleteConcept(smallestByMethod(l, 'allCount)); } } html { _registerThread(); while (!started) sleep(100); try { S _vis = registerVisitor(); int visitorsToday = parseFirstInt(_vis); main.visitors = visitorsToday; //fS cookie = cookieSent(); bool authed = webAuthed(params); print("uri=" + uri); if (forceHttps && eq(uri, "/")) { int port = subBot_currentPort(); print("Port=" + port); if (port != 0 && port != 443) ret hrefresh("https://" + domain()); } if (eq(uri, "/thoughts")) try { loadPage_extraHeaders.set(litmap("X-Forwarded-For", clientIP())); ret hrefresh(10) + loadPage(smartBotURL() + "/thoughts" + (authed ? "?_pretendAuthed=1" : "")); } catch e { printShortException(e); ret hrefresh(10) + hGoogleFontOswald() + hfullcenter("Bot loading..."); } new Matches mm; Conversation conv; { lock dbLock(); if (eq(uri, "/testauth")) ret "Authed: " + yn(authed); if (eq(uri, "/refresh")) { if (!authed) ret "Not authed"; lock downloadLock(); parserModule = null; ret "OK"; } if (swic(uri, "/setSmartBotURL/", mm)) { if (!authed) ret "Not authed"; setSmartBotURL(urldecode(mm.rest())); ret "OK"; } if (eq(uri, "/stats")) ret "Threads: " + ul_htmlEncode(getThreadNames(registeredThreads())); if (eq(uri, "/spam")) ret h3AndTitle("Spam Log") + ul(reversedList(map htmlencode(scanLog("spam.log")))); if (eq(uri, "/logs")) ret withDBLock(func -> S { int step = 100, n = toInt(params.get("n")); L msgs = reversed(allMsgs()); // TODO: optimize new L l; int count = l(msgs); msgs = subList(msgs, n, n+step); for (Msg m : msgs) l.add(formatMsgForLog(m) + "
"); ret h3_htitle("Chat Logs") + pageNav2("/logs", count, n, step, 'n) + p(join(reversed(l))); }); conv = getConv(roomName); if (eq(uri, "/sanity")) { if (conv == null) ret "No conv"; Msg m = last(conv.msgs); int c = conv.allCount(); if (m == null) ret "Msgs: " + l(conv.msgs) + " vs " + c; ret m.nr + " vs " + c; } S weirdip = params.get("weirdip"); if (nempty(weirdip) && authed) { uniq(WeirdIPs).ips.add(weirdip); ret "OK"; } S inspam = params.get("inspam"); S to = params.get("to"); if (possibleGlobalID(inspam) && authed) { Msg m = findWhere(conv.msgs, globalID := inspam); if (m == null) ret "Msg not found"; conv.moveToSpam(m, to); ret "OK, moved to spam"; } S rateParse = params.get("rateParse"); if (possibleGlobalID(rateParse) && authed) { bool value = eq("1", params.get("ok")); Msg m = findWhere(conv.msgs, globalID := rateParse); if (m == null) ret "Msg not found"; m.goodParse = value; change(); ret "OK, rated " + (value ? "good" : "bad"); } S reparse = params.get("reparse"); if (possibleGlobalID(reparse)) { Msg m = findWhere(conv.msgs, globalID := reparse); if (m == null) ret "Msg not found"; S parse = m.parse; parseMsg(m); bool change = neq(parse, m.parse); if (change) { m.goodParse = null; change(); } ret change ? htmlencode("OK -> " + m.parse) : "No change"; } if (eq(uri, "/parse-all") && authed) { time { parseAll(); } ret "OK, " + lastTiming() + " ms"; } S message = trim(params.get("btn")); if (empty(message)) message = trim(params.get("message")); bool botMark = nempty(params.get("botmark")); //print("Have " + l(conv.msgs) + " msgs in conversation " + conv.cookie); if (match("clear", message)) { print("Clearing."); conv.oldDialogs.add(conv.msgs); cset(conv, msgs := new L); conv.change(); message = null; } if (nempty(message) /*&& !lastUserMessageWas(conv, message)*/) postMessage(conv, message, botMark, authed); } // synchronized block if (eq(uri, "/msg")) ret "OK"; if (eq(uri, "/n")) ret str(conv.allCount()); if (eq(uri, "/lastmsg")) ret struct(msgsToJSON(takeLast(1, conv.msgs))); if (eq(uri, "/msgs-from-to")) { int as = conv.archiveSize(); int a = parseInt(params.get('a))-as; int b = parseInt(params.get('b))-as; ret serveText(struct(msgsToJSON(subList(conv.msgs, a, b)))); } if (eq(uri, "/archive-from-to")) { L l = allMsgs(); int a = parseInt(params.get('a))-l(conv.spam); int b = parseInt(params.get('b))-l(conv.spam); ret serveText(struct(msgsToJSON(subList(l, a, b)))); } if (eq(uri, "/archive-regexp")) { fS regexp = params.get("regexp"); fL l = allMsgs(); ret serveText(struct(msgsToJSON(evalWithTimeoutOrNull(10.0, func -> L { Pattern pat = regexpIC(regexp); new L l2; for (Msg m : l) if (safeRegexpFind(pat, m.text)) l2.add(m); ret l2; })))); } if (eq(uri, "/incremental")) { if (uniq(WeirdIPs).ips.contains(clientIP())) { print("Weird IP."); sleepSeconds(10); } int a = parseIntOpt(params.get("a")); int vis = parseIntOpt(params.get("v")); bool json = nempty(params.get('json)); // Funny thing is, JSON isn't even JSON. It's struct() long start = sysNow(); L msgs; bool first = true; while (licensed() && sysNow() < start+longPollMaxWait) { int as = conv.archiveSize(); msgs = cloneSubList(conv.msgs, a-as); //int visitors = main.visitors+random(2); if (empty(msgs) && (vis == 0 || vis == visitors)) { if (first) { print("Long poll starting on " + roomName + ", " + a); first = false; } sleep(longPollTick); } else { int ac = conv.allCount(); if (first) print("Long poll ended. a=" + a + ", as=" + as + ", msgs=" + l(msgs) + ", ac=" + ac); if (json) ret struct/*jsonEncode*/( litorderedmap(n := conv.allCount(), msgs := msgsToJSON(msgs))); new StringBuilder buf; renderMessages(buf, msgs); ret "\n" + (vis == visitors ? "" : "") + buf; } } ret ""; } lock dbLock(); S html = loadSnippet_simpleCache(templateID); S headerHTML = callF(main.headerHTML); if (headerHTML != null) html = replacePartBetweenStrings(html, "", "", headerHTML); new StringBuilder buf; print("Have " + n(conv.msgs, "msg")); renderMessages(buf, conv.msgs); S styles = loadSnippet_simpleCache(#1010888); styles = (S) callF_keepOnNull(stylesPost, styles); html = html.replace("#BOTFONT#", botFont); html = html.replace("#RELATIVESTYLES#", styles); html = html.replace("#N#", str(conv.allCount())); html = html.replace("#VISITORS#", str(visitorsToday)); html = html.replace("#INCREMENTALURL#", relativeRawBotLink(programID(), "incremental"); html = html.replace("#MSGURL#", rawBotLink_rel(programID(), "msg?message=")); // debug mode html = html.replace("var showActions = false;", "var showActions = true;"); int yw = 150, yh = 100; // MAKE MORE S more = hdiv([[]], style := "float: right") // + hsnippetimg(#1101147, width := 113, height := 59, style := "float: right") + h3(hsnippetimg(#1009214, width := 389/2, height := 68/2, title := "BotCompany.de") + " – We make actual AI.") + p(b(ahref("http://tinybrain.de/x30.jar", "The Software.")) + " (Windows/Linux/Mac OS.) Just double-click to run. " + targetBlank("http://java.com/", "Install Java if needed.") + "
" + b(ahref("mailto:info@botcompany.de", "Mail to order a custom chat bot.")) + " " + "Visitors today: " + span(visitorsToday, id := "visnum") + "." + "
" + "Feel free to talk to " + targetBlank("http://smartbot.botcompany.de", b("Smart Bot")) + " in this chat!" + " - " + targetBlank("http://tinybrain.de/1010745", "Smart Bot's Source Code.") + " " + targetBlank("http://javax.tinybrain.de/", "JavaX.") + " " + targetBlank("http://tinybrain.de:8080/getraw.php?id=1012854&contentType=text/html", "Impressum.") + "
" + htmlencode(unicode_rightPointingTriangle()) + " " + targetBlank("http://web-woody-lab.de/", "German smalltalk bot.") + " " + htmlencode(unicode_rightPointingTriangle()) + " " + targetBlank("http://botcompany.de/1008316/raw", "Simple English product bot.")) + tag('table, tr( td(youtubeEmbed("aPQ6Mfa6bEc", yw, yh)) + td(youtubeEmbed("7XCb89RJCcE", yw, yh), style := "padding-left: 10px") + td(youtubeEmbed("AVqqn-lc_Ic", yw, yh), style := "padding-left: 10px") + td(youtubeEmbed("aklRsg59NE8", yw, yh), style := "padding-left: 10px") )) + h3("Bot's Thoughts " + small("(Type " + tt("!word ...") + " to change)")) + tag('iframe, "", src := relativeRawBotLink(programID(), "thoughts"), width:= 700, height := 300) ; html = html.replace("$HEADING", htmlencode(heading) + " | " + targetBlank(selfLink("logs"), "Log")); html = html.replace("", str(buf)); html = html.replace("", more); html = hreplaceTitle(html, heading); html = hmobilefix(html); for (O f : pagePostProcessors) pcall { html = or((S) callF(f, html), html); } ret html; } finally { _unregisterThread(); } } svoid renderMessages(StringBuilder buf, L msgs) { for (Msg m : msgs) { new Matches mm; S html; if (startsWith(m.text, "[IMAGE] ", mm)) { // IMAGE POST S url = trim(mm.get(0)); html = targetBlank(url, himg(url, width := 200, title := "User-submitted image", style := "display: block; margin-left: auto; margin-right: auto")); } else { // TEXT POST //dynamize_linkParams.set(new O[] { target := "_blank" }); //html = dynamize(m.text); html = html_linkURLs_targetBlank(htmlEncode_nlToBr_withIndents(m.text)); } S name = "?"; if (m.user != null) name = targetBlank("http://ai1.space/1008750/?ip=" + urlencode(m.user.ipAddress), m.user.ipAddress, style := "color: white", title := "User's IP Address") + (nempty(m.user.cookie) ? " / " + targetBlank("http://ai1.space/" + m.user.cookie, m.user.cookie, style := "color: white", title := "User's Cookie") : "") + " | " + targetBlank(/*"http://ai1.space/" + m.globalID*/encyclopediaLink(chatLineSymbol + " " + m.nr), /*m.globalID*/m.nr, style := "color: white", title := "Message ID"); S parse = html_linkURLs_targetBlank(htmlEncode_nlToBr_withIndents(unnull(m.parse))); if (webAuthed()) { name += " " + targetBlank(relativeBotLink(programID()) + "?inspam=" + m.globalID, "[spam]"); if (!m.botMark) { name += " " + targetBlank(relativeBotLink(programID()) + "?rateParse=" + m.globalID + "&ok=1", "[gp]"); name += " " + targetBlank(relativeBotLink(programID()) + "?rateParse=" + m.globalID + "&ok=0", "[bp]"); name += " " + targetBlank(relativeBotLink(programID()) + "?reparse=" + m.globalID, "[rp]"); } } renderMessage(buf, name, formatTime(m.time), html, m.globalID, m.user == null ? null : m.user.cookie, m.botMark, m.nr, parse, m.goodParse); } if (empty(msgs)) ret; L buttons = last(msgs).buttons; if (nempty(buttons)) appendButtons(buf, buttons); } static O msgsToJSON(L msgs) { ret map(msgs, func(Msg m) { litorderedmap( time := m.time, text := m.text, ip := m.user.ipAddress, cookie := m.user.cookie, buttons := m.buttons, auth := trueOrNull(m.auth), botMark := trueOrNull(m.botMark), nr := m.nr, globalID := m.globalID ) }); } svoid renderMessage(StringBuilder buf, S name, S time, S html, S id, S cookie, bool botMark, int nr, S parse, Bool goodParse) { ByCookie bc = findConcept(ByCookie, +cookie); S imgID = botMark ? botImageID : #1008359; if (nempty(cookie) && !botMark /*XXX*/ && bc != null) imgID = or(bc.avatarID, imgID); S imgLink = snippetImgLink(imgID); //if (nempty(id)) buf.append(hcomment("Msg ID: " + id)); if (botMark) parse = null; if (nempty(parse)) parse = [[

]] + parse + [[]]; buf.append([[

$NAME
message user image
$TIME
]].replace("$IMG", imgLink) .replace("$NAME", name) .replace("$TIME", time) .replace("#ID#", id) .replace("$STYLE", botMark //? "style='text-align: right'" //? "style='font-style: italic'" ? "style='font-family: " + botFont + "; font-weight: " + botFontWeight + "'" : "") .replace("", unnull(parse)) .replace("", html)); } svoid appendButtons(StringBuilder buf, L buttons) { S buttonsHtml = lines(map(buttons, func(S text) { hsubmit(text, name := "btn") })); buf.append([[
$BUTTONS
]].replace("$BUTTONS", buttonsHtml); } svoid appendDate(StringBuilder buf, S date) { buf.append([[
DATE
]].replace("DATE", date)); } sS formatTime(long time) { ret formatGMTWithOptionalDate_24(time); } sS formatMsgForLog(Msg m) { ret htmlencode(m.nr + " " + formatDateAndTime(m.time)) + " - " + htmlencode(m.user.ipAddress + ": " + m.text); } static Conversation getConv(fS cookie) { ret uniq_sync(Conversation, +cookie); } static L allMsgs() { new L l; for (Conversation c) { for (L msgs : cloneList(c.oldDialogs)) addAll(l, msgs); addAll(l, c.msgs); } ret l; } svoid processMsgCommands(Msg msg) { new Matches m; if (swic(msg.text, "avatar ", m)) { S avatarID = fsI(trim($1)); BufferedImage img = loadImage2(avatarID); if (img.getWidth() <= 400 && img.getHeight() <= 400) cset(uniq(ByCookie, cookie := msg.user.cookie), +avatarID); else fail("Avatar too big: " + avatarID); } } svoid postMessage(Conversation conv, S message, bool botMark, bool authed) { message = shorten(message, messageLimit, " [...]"); S ip = clientIP(); print("Have message from " + ip + " at " + gmtWithSeconds() + " in thread " + currentThread() + ": " + quote(shorten(message, 80))); if (startsWith(ip, "66.249.65.")) if (matchOneOf(message, "web * is invalid", "web * is correct")) ret; // anti hammer mechanism S key = ip + " " + botMark; if (eq(lastPosted.get(key), message)) { print("Skipping same."); ret; } lastPosted.put(key, message); if (postingDisabled) ret; if (!botMark && !authed && superSimpleSpamTester(message)) { print(logStructureWithDate("spam.log", "Ignoring spam message: " + quote(message))); ret; } // ADD NOT SPAM MESSAGE Msg msg = new Msg(clientIP(), cookieConcept(), message); msg.botMark = botMark; msg.auth = authed; //msg.nr = toInt(getOpt(last(conv.msgs), 'nr))+1; if (conv.counter == 0) cset(conv, counter := conv.allCount()); cset(conv, counter := conv.counter+1); msg.nr = conv.counter; parseMsg(msg); conv.add(msg); print("Have " + l(conv.msgs) + " msgs in conversation " + conv.cookie + " after add"); } svoid parseAll { for (Msg m : allMsgs()) { m.parse = null; parseMsg(m); } change(); } svoid parseMsg(Msg msg) { { if (!showParses) ret; lock downloadLock(); if (parserModule == null) parserModule = hotwire(#1012396); } pcall { if (countLines(msg.text) > 1) msg.parse = null; else msg.parse = (S) callOpt(parserModule, 'ai_tripelizeAndRenderTriple, msg.text); } }