static S myBotName; static long botRoom = 1; static new Matches m; // for matching lines static int historyGrab = 10; // read last 10 lines from history static new L msgs; static long lastSucked; // timestamp static long suckDelay = 2000; // suck every X ms static int maxLineLength = 1024; // should match groupchat.json.botsay.php static boolean loop_firstTime = true; static int loopDelay = 5000; static boolean debug_printIncoming; static O externalSucker, externalPoster; static TestRoom testRoom; // Set for testing static class TestRoom { long roomID; new L msgs; new L presences; void post(S name, S text) { testPost(name, text, true); } void testPost(S name, S text, boolean isBot) { new ChatLine line; line.room = roomID; line.id = l(msgs)+1; line.who = name; line.text = text; line.isBot = isBot; msgs.add(line); } } static void testMode() { testMode(1); } static synchronized void testMode(long roomID) { testRoom = new TestRoom; testRoom.roomID = roomID; } static void enterRoom(long roomID) { if (testRoom != null) ret; // Do nothing on testing botRoom = roomID; // TODO: presence? lastSucked = 0; msgs = new L; } static void changeName(S name) { myBotName = unnull(name); } static S botifyName(S name) { name = unnull(name); if (!name.endsWith("bot")) name += " Bot"; ret name; } static S getMyName() { S name = myBotName; if (empty(name)) name = getProgramName(); ret botifyName(name); } static synchronized void say(S text) { text = cleanLine(text); S name = getMyName(); print(">> " + (testRoom != null ? "TEST" : "") + " " + roomNr() + " > " + name + " > " + text); if (testRoom != null) testRoom.post(name, text); else if (externalPoster != null) call(externalPoster, "say", name, text, mc()); else sayInWebChatRoom(name, text, botRoom); } // should match server's line processing - so we can recognize // our own lines static S cleanLine(S line) { line = unnull(line); if (l(line) > maxLineLength) { print("Warning: Cutting line from " + l(line) + " to " + maxLineLength + " chars."); line = substring(line, 0, maxLineLength); } ret line.trim(); } static void suckChat() { if (testRoom != null) { addMsgs(subList(testRoom.msgs, l(msgs))); ret; } ChatLine last = last(msgs); long lastID = last == null ? 0 : last.id; if (externalSucker != null) { addMsgs((L) quickImport(call(externalSucker, "getNewMessages", lastID))); ret; } if (now() < lastSucked + suckDelay) ret; try { L newMsgs = suckWebChatRoom(getMyName(), botRoom, lastID, lastID == 0 ? historyGrab : 1000); addMsgs(newMsgs); } finally { lastSucked = now(); } } static void addMsgs(L l) { if (l == null) ret; if (debug_printIncoming) for (ChatLine line : l) print("INCOMING: " + line.who + " > " + line.text); for (ChatLine line : l) { hackfixUmlauts(line); msgs.add(line); } } // ISO to UTF-8 (lines coming from chat) - broken I think static void hackfixUmlauts(ChatLine line) { /* S s = line.text; new StringBuilder buf; for (int i = 0; i < l(s); i++) { int c = (int) s.charAt(i); if (c == 252) c = 195; // ΓΌ buf.append((char) c); } line.text = str(buf); */ } static class MsgMarker { long room, idSeen; void advance(ChatLine line) { if (line == null) ret; idSeen = max(idSeen, line.id); } void setRoom(long roomID) { if (room != roomID) { room = roomID; idSeen = 0; } } } static L msgsFrom(MsgMarker marker) { marker.setRoom(botRoom); ret msgsFrom(marker.idSeen); } static synchronized L msgsFrom(long idSeen) { int i = l(msgs); while (i > 0 && msgs.get(i-1).id > idSeen) --i; L l = subList(msgs, i); ret l; } static class WordDropper extends S2S { Set wordSet; *(S words) { wordSet = dropWords_preprocess(words); } S get(S s) { ret dropWords_impl(s, wordSet); } } static S getWordDropper_c1; static S2S getWordDropper_c2; static S2S getWordDropper(S wordsToDrop) { if (neq(getWordDropper_c1, wordsToDrop)) { getWordDropper_c1 = wordsToDrop; getWordDropper_c2 = new WordDropper(wordsToDrop); } ret getWordDropper_c2; } static new MsgMarker matchQuestion_seen; static ChatLine matchQuestion_msg; static boolean matchQuestion_dropping(S pat, S wordsToDrop) { ret matchQuestion(pat, getWordDropper(wordsToDrop)); } static boolean matchQuestion(S pat) { ret matchQuestion(pat, null); } static boolean matchQuestion(S pat, S2S preprocessor) { suckChat(); matchQuestion_msg = null; for (ChatLine line : msgsFrom(matchQuestion_seen)) { matchQuestion_seen.advance(line); S text = preprocess(line.text, preprocessor); if (debug_printIncoming) print("matchQuestion " + quote(text)); if (match(pat, text, m)) { matchQuestion_msg = line; ret true; } } ret false; } static S whoAsks() { ret matchQuestion_msg.who; } static boolean haveAnswered(MsgMarker marker, S answer) { S myName = getMyName(); for (ChatLine line : msgsFrom(marker)) if (eq(line.who, myName) && eq(line.text, answer)) ret true; ret false; } static void answerQuestion(S answer) { if (haveAnswered(matchQuestion_seen, answer)) print("Have answered already :)"); else say(answer); } static long roomNr() { ret botRoom; } static S roomNrString() { ret str(botRoom); } static boolean loop() { if (!licensed()) ret false; if (loop_firstTime) loop_firstTime = false; else if (testRoom != null) ret false; else sleep(loopDelay); ret true; } static synchronized void testPost(S name, S text, boolean isUser) { if (testRoom != null) testRoom.testPost(name, text, !isUser); } static void userPost(S text) { testPost("User", text, true); } // for test mode - get my responses static synchronized L getResponses() { new L l; if (testRoom == null) ret l; S name = getMyName(); for (ChatLine line : testRoom.msgs) { if (eq(line.who, name) && line.isBot) l.add(line.text); } ret l; } // string-to-string function static abstract class S2S { abstract S get(S s); } static S preprocess(S text, S2S preprocessor) { pcall { if (preprocessor != null) text = preprocessor.get(text); } ret text; } static void printIncoming() { debug_printIncoming = true; } static synchronized L getPresentNicks() { if (testRoom != null) ret cloneList(testRoom.presences); // TODO: cache this for a bit? ret getWebChatPresences(roomNr()); }