Transpiled version (5456L) is out of date.
| 1 | !752 | 
| 2 | |
| 3 | static boolean actuallyPost = true; | 
| 4 | |
| 5 | static L<S> botIDs = litlist("#1001890", "#1001923", "#1001937", "#1001942", "#1001945", "#1001951" /*, "#1001747"*/,
 | 
| 6 | "#1001989", "#1001861", "#1001991", "#1001994", "#1002007"); | 
| 7 | static new L<Class> bots; | 
| 8 | static new L<S> activeBots; | 
| 9 | static Map<S, Class> botsByID = synchroTreeMap(); | 
| 10 | |
| 11 | static S channelID = "C0FH9PY8J"; // relp, #talkingbots | 
| 12 | static S token; | 
| 13 | |
| 14 | //static new Map<S, S> lastPostByUser; | 
| 15 | static new Map<S, S> userNames; | 
| 16 | |
| 17 | static S lastQuestion, lastAnswer; | 
| 18 | static S lastAnswerTimestamp; // only for posted in this session | 
| 19 | static int lastAnswerScore; | 
| 20 | |
| 21 | static int webServerPort = 80; // set to 0 for no web serving | 
| 22 | static long hitCount; | 
| 23 | |
| 24 | /* | 
| 25 | static class Interaction {
 | 
| 26 | S question, user, answertime, answer; | 
| 27 | long realtime; | 
| 28 | int score; // not used yet | 
| 29 | } | 
| 30 | |
| 31 | static new L<Interaction> interactions; | 
| 32 | */ | 
| 33 | static new L<Map> history; // stores all processed user questions (not whole chat log) | 
| 34 | |
| 35 | p {
 | 
| 36 | if (token == null) | 
| 37 |     token = loadSecretTextFileMandatory("#1001889", "relp-slack-botstuff-token").trim();
 | 
| 38 | |
| 39 | reload(); | 
| 40 | readLog(); | 
| 41 | |
| 42 |   if (webServerPort != 0) {
 | 
| 43 |     readLocally("hitCount");
 | 
| 44 | serveHttp(webServerPort); | 
| 45 | } | 
| 46 | |
| 47 |   while true {
 | 
| 48 |     pcall {
 | 
| 49 |       if (lastAnswerTimestamp != null) {
 | 
| 50 | // Poll reactions to our last answer - is there a more efficient way? | 
| 51 | int score = slackReactionScore(token, channelID, lastAnswerTimestamp); | 
| 52 |         if (score != lastAnswerScore) {
 | 
| 53 |           print("Reaction score changes to " + score);
 | 
| 54 | recordFeedback(lastQuestion, lastAnswer, lastAnswerTimestamp, "emoji", score-lastAnswerScore); | 
| 55 | lastAnswerScore = score; | 
| 56 | } | 
| 57 | } | 
| 58 | |
| 59 | L<SlackMsg> qs = getNewQuestions(); | 
| 60 | //print(l(qs) + " question(s)."); | 
| 61 | //printList(qs); | 
| 62 |       for (SlackMsg msg : qs) {
 | 
| 63 | S q = msg.text; | 
| 64 | S userName = getUserName(msg.user); | 
| 65 |         print("Processing question: " + quote(q));
 | 
| 66 |         if (lastAnswer != null) { // TODO: maybe check the time delay
 | 
| 67 | int score = feedbackScore(q); | 
| 68 |           if (score != 0) {
 | 
| 69 | recordFeedback(lastQuestion /*lastPostByUser.get(userName)*/, lastAnswer, lastAnswerTimestamp, q, score); | 
| 70 | if (score > 0) | 
| 71 |               pcall { slackSmile(token, channelID, msg.ts); }
 | 
| 72 | else | 
| 73 |               pcall { slackReact(token, channelID, msg.ts, "thinking_face"); }
 | 
| 74 | } | 
| 75 | lastAnswer = null; | 
| 76 | } | 
| 77 | //if (userName != null) lastPostByUser.put(userName, msg.text); | 
| 78 |         pcall {
 | 
| 79 | S a = answer(q); | 
| 80 |           if (a != null) {
 | 
| 81 | if (userName != null) | 
| 82 | a = "<@" + userName + "> " + a; | 
| 83 |             print("Answering with: " + quote(a));
 | 
| 84 | lastAnswer = a; | 
| 85 | lastQuestion = q; | 
| 86 |             if (actuallyPost) {
 | 
| 87 | S answerTime = postAnswer(a); | 
| 88 |               Map logEntry = litmap("question", q, "user", userName, "answertime", answerTime, "answer", a, "realtime", now());
 | 
| 89 | history.add(logEntry); | 
| 90 | logQuoted(new File(programDir(), "memory-log"), structure(logEntry)); | 
| 91 | } | 
| 92 | } | 
| 93 | } | 
| 94 | } | 
| 95 | } | 
| 96 | sleepSeconds(5); | 
| 97 | } | 
| 98 | } | 
| 99 | |
| 100 | // returns the slack timestamp | 
| 101 | static S postAnswer(S text) {
 | 
| 102 | S username = "botstuff"; | 
| 103 | S url = "https://slack.com/api/chat.postMessage"; | 
| 104 | S postData = "token=" + urlencode(token) + "&channel=" + urlencode(channelID) + "&text=" + urlencode(text) | 
| 105 | + "&username=" + urlencode(username); | 
| 106 | print(postData); | 
| 107 | S data = doPost(postData, url); | 
| 108 | Map map = jsonDecodeMap(data); | 
| 109 |   lastAnswerTimestamp = (S) map.get("ts");
 | 
| 110 | lastAnswerScore = 0; | 
| 111 |   print("postAnswer: " + data);
 | 
| 112 | ret lastAnswerTimestamp; | 
| 113 | } | 
| 114 | |
| 115 | static S getNewQuestions_lastSeen; | 
| 116 | |
| 117 | static L<SlackMsg> getNewQuestions() {
 | 
| 118 | // Get 150 on first call (history before bot start) | 
| 119 | // and 1000 thereafter (just grab anything new) | 
| 120 | int limit = getNewQuestions_lastSeen == null ? 150 : 1000; | 
| 121 | L<SlackMsg> msgs = slackReadChannel(channelID, token, limit, getNewQuestions_lastSeen); | 
| 122 | if (!msgs.isEmpty()) getNewQuestions_lastSeen = last(msgs).ts; | 
| 123 | int i = indexOfLastBotAnswer(msgs); | 
| 124 |   if (i >= 0) {
 | 
| 125 |     print("Last bot answer at " + (i+1) + "/" + l(msgs) + ": " + structure(msgs.get(i)));
 | 
| 126 | |
| 127 | // update variables | 
| 128 | lastAnswer = msgs.get(i).text; | 
| 129 |     for (int j = 0; j < i; j++) {
 | 
| 130 | SlackMsg msg = msgs.get(j); | 
| 131 | S userName = getUserName(msg.user); | 
| 132 | //if (userName != null) lastPostByUser.put(userName, msg.text); | 
| 133 | } | 
| 134 | |
| 135 | msgs = subList(msgs, i+1); | 
| 136 | } | 
| 137 | ret msgs; | 
| 138 | } | 
| 139 | |
| 140 | static int indexOfLastBotAnswer(L<SlackMsg> msgs) {
 | 
| 141 | for (int i = l(msgs)-1; i >= 0; i--) | 
| 142 | if (msgs.get(i).botName != null) ret i; | 
| 143 | ret -1; | 
| 144 | } | 
| 145 | |
| 146 | static synchronized S answer(S s) {
 | 
| 147 | new Matches m; | 
| 148 | |
| 149 |   if (match("reload", s)) {
 | 
| 150 | reload(); | 
| 151 | ret l(bots) + "/" + l(botIDs) + " bots reloaded."; | 
| 152 | } | 
| 153 | |
| 154 |   if (match("reload *", s, m)) {
 | 
| 155 | reload(m.unq(0)); | 
| 156 | ret "Bot " + formatSnippetID(m.unq(0)) + " reloaded."; | 
| 157 | } | 
| 158 | |
| 159 |   if (match("list bots", s)) {
 | 
| 160 | S answer = "Active bots: " + structure(activeBots); | 
| 161 | L<S> inactiveBots = diff(botIDs, activeBots); | 
| 162 | if (!inactiveBots.isEmpty()) | 
| 163 | answer += ". Inactive bots: " + structure(inactiveBots); | 
| 164 | ret answer; | 
| 165 | } | 
| 166 | |
| 167 |   if (match("test", s))
 | 
| 168 | ret "blah!"; | 
| 169 | |
| 170 | ret callStaticAnswerMethod(bots, s); | 
| 171 | } | 
| 172 | |
| 173 | static void recordFeedback(S probableQuestion, S answer, S answerTime, S feedback, int score) {
 | 
| 174 |   Map data = litmap("realtime", now(), "question", probableQuestion, "answer", answer, "answertime", answerTime, "feedback", feedback, "score", score);
 | 
| 175 |   print("Recording feedback: " + data);
 | 
| 176 | logQuoted(new File(programDir(), "feedback-log"), structure(data)); | 
| 177 | logQuoted(new File(programDir(), "memory-log"), structure(data)); | 
| 178 | } | 
| 179 | |
| 180 | static void reload() {
 | 
| 181 |   botIDs = or((L<S>) loadVariableDefinition("botIDs"), botIDs);
 | 
| 182 | cleanUp(bots); | 
| 183 | activeBots = new ArrayList<S>(); | 
| 184 | botsByID.clear(); | 
| 185 | for (S botID : botIDs) | 
| 186 |     pcall {
 | 
| 187 | loadBot(botID); | 
| 188 | } | 
| 189 | } | 
| 190 | |
| 191 | static void reload(S botID) {
 | 
| 192 | S parsedID = "" + parseSnippetID(botID); | 
| 193 | Class bot = botsByID.get(parsedID); | 
| 194 | if (bot == null) ret; | 
| 195 | cleanUp(bot); | 
| 196 | botsByID.remove(parsedID); | 
| 197 | activeBots.remove(bot); | 
| 198 | loadBot(botID); | 
| 199 | } | 
| 200 | |
| 201 | static void loadBot(S botID) {
 | 
| 202 |   print("Loading bot: " + botID);
 | 
| 203 | Class c = run(botID); | 
| 204 | bots.add(c); | 
| 205 | activeBots.add(botID); // only if loading doesn't fail | 
| 206 |   botsByID.put("" + parseSnippetID(botID), c);
 | 
| 207 |   print("Loaded bot: " + botID + ", bots now: " + l(activeBots));
 | 
| 208 | } | 
| 209 | |
| 210 | static S getUserName(S userID) {
 | 
| 211 | if (userID == null) ret null; | 
| 212 | S userName = userNames.get(userID); | 
| 213 |   if (userName == null) pcall {
 | 
| 214 |     userName = (S) slackGetUserInfo(token, userID).get("name");
 | 
| 215 | userNames.put(userID, userName); | 
| 216 | } | 
| 217 | ret userName; | 
| 218 | } | 
| 219 | |
| 220 | static NanoHTTPD.Response serve(S uri, NanoHTTPD.Method method, | 
| 221 |   Map<S,S> header, Map<S,S> parms, Map<S,S> files) {
 | 
| 222 |   print("Serving HTTP " + quote(uri));
 | 
| 223 | ++hitCount; | 
| 224 |   saveLocally("hitCount");
 | 
| 225 |   uri = dropPrefixMandatory("/", uri);
 | 
| 226 | if (eq(uri, "")) | 
| 227 | ret serveHomePage(); | 
| 228 | if (eq(uri, "favicon.ico")) | 
| 229 | ret serve404(); | 
| 230 | |
| 231 |   int i = uri.indexOf('/');
 | 
| 232 | S firstPart = i >= 0 ? uri.substring(0, i) : uri; | 
| 233 | S rest = i >= 0 ? substr(uri, i) : "/"; // rest always starts with "/" | 
| 234 | ret serveBot(firstPart, rest); | 
| 235 | } | 
| 236 | |
| 237 | static NanoHTTPD.Response serveBot(S botID, S subUri) {
 | 
| 238 |   Class bot = botsByID.get("" + parseSnippetID(botID));
 | 
| 239 |   boolean raw = subUri.equals("/raw") || subUri.startsWith("/raw/");
 | 
| 240 |   if (raw) {
 | 
| 241 | subUri = substr(subUri, "/raw".length()); | 
| 242 | if (subUri.length() == 0) subUri = "/"; | 
| 243 | } | 
| 244 |   if (bot != null) {
 | 
| 245 | S botHtml = callHtmlMethod(bot, subUri); | 
| 246 | if (raw) ret serveHTML(unnull(botHtml)); | 
| 247 | if (botHtml == null) | 
| 248 | botHtml = "Bot has no HTML output."; | 
| 249 | S title = "TinyBrain Bot " + formatSnippetID(botID); | 
| 250 | S html = "<html><head><title>" + title + "</title></head>" + | 
| 251 | "<body><h3>Bot <a href=\"http://tinybrain.de/" + parseSnippetID(botID) + "\">" + formatSnippetID(botID) + "</a></h3>\n" + botHtml + | 
| 252 | "\n</body></html>"; | 
| 253 | ret serveHTML(html); | 
| 254 | } | 
| 255 | |
| 256 | ret serve404(); | 
| 257 | } | 
| 258 | |
| 259 | static NanoHTTPD.Response serveHomePage() {
 | 
| 260 | new StringBuilder buf; | 
| 261 |   buf.append("<html>");
 | 
| 262 |   buf.append("<head><title>TinyBrain Chat Bots</title></head>");
 | 
| 263 |   buf.append("<body>");
 | 
| 264 |   buf.append("<h3><a href=" + htmlQuote("http://tinybrain.de") + ">TinyBrain</a> Bots</h3>");
 | 
| 265 |   buf.append("<ul>");
 | 
| 266 |   for (S botID : activeBots) {
 | 
| 267 | botID = formatSnippetID(botID); | 
| 268 | S parsedID = "" + parseSnippetID(botID); | 
| 269 | S title = "?"; | 
| 270 |     pcall { title = getSnippetTitle_overBot(botID); }
 | 
| 271 | S name = botID + " - " + htmlencode(title); | 
| 272 |     buf.append("<li><a href=" + htmlQuote(parsedID) + ">" + name + "</a> (");
 | 
| 273 |     buf.append("<a href=" + htmlQuote("http://tinybrain.de/" + parsedID) + ">source</a>)</li>");
 | 
| 274 | } | 
| 275 |   buf.append("</ul>");
 | 
| 276 |   buf.append("<p>Hit count: " + hitCount + "</p>");
 | 
| 277 | |
| 278 |   buf.append("<p>Last interactions:</p>");
 | 
| 279 | |
| 280 | int maxInteractionsToPrint = 10; | 
| 281 | |
| 282 |   for (int i = l(history)-1; i >= 0 && i >= l(history)-maxInteractionsToPrint; i--) {
 | 
| 283 | Map m = history.get(i); | 
| 284 |     buf.append("<p>" + htmlencode(m.get("question")) + "<br>    " + htmlencode(m.get("answer")) + "</p>");
 | 
| 285 | } | 
| 286 | |
| 287 |   buf.append("</body>");
 | 
| 288 |   buf.append("</html>");
 | 
| 289 | ret serveHTML(buf); | 
| 290 | } | 
| 291 | |
| 292 | static S getSnippetTitle_overBot(S snippetID) {
 | 
| 293 |   startBot("Snippet Title Bot", "#1001747");
 | 
| 294 | // Use local version | 
| 295 | //S s = sendToLocalBot(getVMPort() + "/Snippet Title Bot", "what is the title of snippet *", snippetID); | 
| 296 | // Use bot VM version | 
| 297 |   S s = sendToLocalBot_cached("Snippet Title Bot", "what is the title of snippet *", snippetID);
 | 
| 298 | new Matches m; | 
| 299 |   if (match("The title of snippet * is *.", s, m))
 | 
| 300 | ret m.unq(1); | 
| 301 |   throw fail("Bot said: " + s);
 | 
| 302 | } | 
| 303 | |
| 304 | static void readLog() {
 | 
| 305 |   for (S s : scanLog("#1001915", "memory-log")) pcall {
 | 
| 306 | history.add((Map) safeUnstructure(s)); | 
| 307 | } | 
| 308 | print(l(history) + " history entries."); | 
| 309 | } | 
Began life as a copy of #1001912
download show line numbers debug dex old transpilations
Travelled to 16 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, jtubtzbbkimh, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, teubizvjbppd, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
| Snippet ID: | #1001915 | 
| Snippet name: | Slack Bot! (not used anymore. relp.slack.com, #talkingbots, can be redirected to other channel) | 
| Eternal ID of this version: | #1001915/1 | 
| Text MD5: | 18ec645b1afc97e73478c26771190c76 | 
| Author: | stefan | 
| Category: | |
| Type: | JavaX source code | 
| Public (visible to everyone): | Yes | 
| Archived (hidden from active list): | No | 
| Created/modified: | 2015-12-14 19:21:12 | 
| Source code size: | 10137 bytes / 309 lines | 
| Pitched / IR pitched: | No / Yes | 
| Views / Downloads: | 1234 / 2494 | 
| Referenced in: | [show references] |