Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

309
LINES

< > BotCompany Repo | #1001915 // Slack Bot! (not used anymore. relp.slack.com, #talkingbots, can be redirected to other channel)

JavaX source code [tags: use-pretranspiled] - run with: x30.jar

Transpiled version (5456L) is out of date.

!752

static boolean actuallyPost = true;

static L<S> botIDs = litlist("#1001890", "#1001923", "#1001937", "#1001942", "#1001945", "#1001951" /*, "#1001747"*/,
  "#1001989", "#1001861", "#1001991", "#1001994", "#1002007");
static new L<Class> bots;
static new L<S> activeBots;
static Map<S, Class> botsByID = synchroTreeMap();

static S channelID = "C0FH9PY8J"; // relp, #talkingbots
static S token;

//static new Map<S, S> lastPostByUser;
static new Map<S, S> userNames;

static S lastQuestion, lastAnswer;
static S lastAnswerTimestamp; // only for posted in this session
static int lastAnswerScore;

static int webServerPort = 80; // set to 0 for no web serving
static long hitCount;

/*
static class Interaction {
  S question, user, answertime, answer;
  long realtime;
  int score; // not used yet
}

static new L<Interaction> interactions;
*/
static new L<Map> history; // stores all processed user questions (not whole chat log)

p {
  if (token == null)
    token = loadSecretTextFileMandatory("#1001889", "relp-slack-botstuff-token").trim();

  reload();
  readLog();
  
  if (webServerPort != 0) {
    readLocally("hitCount");
    serveHttp(webServerPort);
  }
  
  while true {
    pcall {
      if (lastAnswerTimestamp != null) {
        // Poll reactions to our last answer - is there a more efficient way?
        int score = slackReactionScore(token, channelID, lastAnswerTimestamp);
        if (score != lastAnswerScore) {
          print("Reaction score changes to " + score);
          recordFeedback(lastQuestion, lastAnswer, lastAnswerTimestamp, "emoji", score-lastAnswerScore);
          lastAnswerScore = score;
        }
      }
      
      L<SlackMsg> qs = getNewQuestions();
      //print(l(qs) + " question(s).");
      //printList(qs);
      for (SlackMsg msg : qs) {
        S q = msg.text;
        S userName = getUserName(msg.user);
        print("Processing question: " + quote(q));
        if (lastAnswer != null) { // TODO: maybe check the time delay
          int score = feedbackScore(q);
          if (score != 0) {
            recordFeedback(lastQuestion /*lastPostByUser.get(userName)*/, lastAnswer, lastAnswerTimestamp, q, score);
            if (score > 0)
              pcall { slackSmile(token, channelID, msg.ts); }
            else
              pcall { slackReact(token, channelID, msg.ts, "thinking_face"); }
          }
          lastAnswer = null;
        }
        //if (userName != null) lastPostByUser.put(userName, msg.text);
        pcall {
          S a = answer(q);
          if (a != null) {
            if (userName != null)
              a = "<@" + userName + "> " + a;
            print("Answering with: " + quote(a));
            lastAnswer = a;
            lastQuestion = q;
            if (actuallyPost) {
              S answerTime = postAnswer(a);
              Map logEntry = litmap("question", q, "user", userName, "answertime", answerTime, "answer", a, "realtime", now());
              history.add(logEntry);
              logQuoted(new File(programDir(), "memory-log"), structure(logEntry));
            }
          }
        }
      }
    }
    sleepSeconds(5);
  }
}

// returns the slack timestamp
static S postAnswer(S text) {
  S username = "botstuff";
  S url = "https://slack.com/api/chat.postMessage";
  S postData = "token=" + urlencode(token) + "&channel=" + urlencode(channelID) + "&text=" + urlencode(text)
    + "&username=" + urlencode(username);
  print(postData);
  S data = doPost(postData, url);
  Map map = jsonDecodeMap(data);
  lastAnswerTimestamp = (S) map.get("ts");
  lastAnswerScore = 0;
  print("postAnswer: " + data);
  ret lastAnswerTimestamp;
}

static S getNewQuestions_lastSeen;

static L<SlackMsg> getNewQuestions() {
  // Get 150 on first call (history before bot start)
  // and 1000 thereafter (just grab anything new)
  int limit = getNewQuestions_lastSeen == null ? 150 : 1000;
  L<SlackMsg> msgs = slackReadChannel(channelID, token, limit, getNewQuestions_lastSeen);
  if (!msgs.isEmpty()) getNewQuestions_lastSeen = last(msgs).ts;
  int i = indexOfLastBotAnswer(msgs);
  if (i >= 0) {
    print("Last bot answer at " + (i+1) + "/" + l(msgs) + ": " + structure(msgs.get(i)));
    
    // update variables
    lastAnswer = msgs.get(i).text;
    for (int j = 0; j < i; j++) {
      SlackMsg msg = msgs.get(j);
      S userName = getUserName(msg.user);
      //if (userName != null) lastPostByUser.put(userName, msg.text);
    }

    msgs = subList(msgs, i+1);
  }
  ret msgs;
}

static int indexOfLastBotAnswer(L<SlackMsg> msgs) {
  for (int i = l(msgs)-1; i >= 0; i--)
    if (msgs.get(i).botName != null) ret i;
  ret -1;
}

static synchronized S answer(S s) {
  new Matches m;
  
  if (match("reload", s)) {
    reload();
    ret l(bots) + "/" + l(botIDs) + " bots reloaded.";
  }
  
  if (match("reload *", s, m)) {
    reload(m.unq(0));
    ret "Bot " + formatSnippetID(m.unq(0)) + " reloaded.";
  }
  
  if (match("list bots", s)) {
    S answer = "Active bots: " + structure(activeBots);
    L<S> inactiveBots = diff(botIDs, activeBots);
    if (!inactiveBots.isEmpty())
      answer += ". Inactive bots: " + structure(inactiveBots);
    ret answer;
  }
    
  if (match("test", s))
    ret "blah!";
    
  ret callStaticAnswerMethod(bots, s);
}

static void recordFeedback(S probableQuestion, S answer, S answerTime, S feedback, int score) {
  Map data = litmap("realtime", now(), "question", probableQuestion, "answer", answer, "answertime", answerTime, "feedback", feedback, "score", score);
  print("Recording feedback: " + data);
  logQuoted(new File(programDir(), "feedback-log"), structure(data));
  logQuoted(new File(programDir(), "memory-log"), structure(data));
}

static void reload() {
  botIDs = or((L<S>) loadVariableDefinition("botIDs"), botIDs);
  cleanUp(bots);
  activeBots = new ArrayList<S>();
  botsByID.clear();
  for (S botID : botIDs)
    pcall {
      loadBot(botID);
    }
}

static void reload(S botID) {
  S parsedID = "" + parseSnippetID(botID);
  Class bot = botsByID.get(parsedID);
  if (bot == null) ret;
  cleanUp(bot);
  botsByID.remove(parsedID);
  activeBots.remove(bot);
  loadBot(botID);
}

static void loadBot(S botID) {
  print("Loading bot: " + botID);
  Class c = run(botID);
  bots.add(c);
  activeBots.add(botID); // only if loading doesn't fail
  botsByID.put("" + parseSnippetID(botID), c);
  print("Loaded bot: " + botID + ", bots now: " + l(activeBots));
}

static S getUserName(S userID) {
  if (userID == null) ret null;
  S userName = userNames.get(userID);
  if (userName == null) pcall {
    userName = (S) slackGetUserInfo(token, userID).get("name");
    userNames.put(userID, userName);
  }
  ret userName;
}

static NanoHTTPD.Response serve(S uri, NanoHTTPD.Method method,
  Map<S,S> header, Map<S,S> parms, Map<S,S> files) {
  print("Serving HTTP " + quote(uri));
  ++hitCount;
  saveLocally("hitCount");
  uri = dropPrefixMandatory("/", uri);
  if (eq(uri, ""))
    ret serveHomePage();
  if (eq(uri, "favicon.ico"))
    ret serve404();
  
  int i = uri.indexOf('/');
  S firstPart = i >= 0 ? uri.substring(0, i) : uri;
  S rest = i >= 0 ? substr(uri, i) : "/"; // rest always starts with "/"
  ret serveBot(firstPart, rest);
}

static NanoHTTPD.Response serveBot(S botID, S subUri) {
  Class bot = botsByID.get("" + parseSnippetID(botID));
  boolean raw = subUri.equals("/raw") || subUri.startsWith("/raw/");
  if (raw) {
    subUri = substr(subUri, "/raw".length());
    if (subUri.length() == 0) subUri = "/";
  }
  if (bot != null) {
    S botHtml = callHtmlMethod(bot, subUri);
    if (raw) ret serveHTML(unnull(botHtml));
    if (botHtml == null)
      botHtml = "Bot has no HTML output.";
    S title = "TinyBrain Bot " + formatSnippetID(botID);
    S html = "<html><head><title>" + title + "</title></head>" +
      "<body><h3>Bot <a href=\"http://tinybrain.de/" + parseSnippetID(botID) + "\">" + formatSnippetID(botID) + "</a></h3>\n" + botHtml +
      "\n</body></html>";
    ret serveHTML(html);
  }
  
  ret serve404();
}

static NanoHTTPD.Response serveHomePage() {
  new StringBuilder buf;
  buf.append("<html>");
  buf.append("<head><title>TinyBrain Chat Bots</title></head>");
  buf.append("<body>");
  buf.append("<h3><a href=" + htmlQuote("http://tinybrain.de") + ">TinyBrain</a> Bots</h3>");
  buf.append("<ul>");
  for (S botID : activeBots) {
    botID = formatSnippetID(botID);
    S parsedID = "" + parseSnippetID(botID);
    S title = "?";
    pcall { title = getSnippetTitle_overBot(botID); }
    S name = botID + " - " + htmlencode(title);
    buf.append("<li><a href=" + htmlQuote(parsedID) + ">" + name + "</a> (");
    buf.append("<a href=" + htmlQuote("http://tinybrain.de/" + parsedID) + ">source</a>)</li>");
  }
  buf.append("</ul>");
  buf.append("<p>Hit count: " + hitCount + "</p>");
  
  buf.append("<p>Last interactions:</p>");
  
  int maxInteractionsToPrint = 10;
  
  for (int i = l(history)-1; i >= 0 && i >= l(history)-maxInteractionsToPrint; i--) {
    Map m = history.get(i);
    buf.append("<p>" + htmlencode(m.get("question")) + "<br>&nbsp; &nbsp; " + htmlencode(m.get("answer")) + "</p>");
  }
  
  buf.append("</body>");
  buf.append("</html>");
  ret serveHTML(buf);
}

static S getSnippetTitle_overBot(S snippetID) {
  startBot("Snippet Title Bot", "#1001747");
  // Use local version
  //S s = sendToLocalBot(getVMPort() + "/Snippet Title Bot", "what is the title of snippet *", snippetID);
  // Use bot VM version
  S s = sendToLocalBot_cached("Snippet Title Bot", "what is the title of snippet *", snippetID);
  new Matches m;
  if (match("The title of snippet * is *.", s, m))
    ret m.unq(1);
  throw fail("Bot said: " + s);
}

static void readLog() {
  for (S s : scanLog("#1001915", "memory-log")) pcall {
    history.add((Map) safeUnstructure(s));
  }
  print(l(history) + " history entries.");
}

Author comment

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: 959 / 2168
Referenced in: #1001915 - Slack Bot! (not used anymore. relp.slack.com, #talkingbots, can be redirected to other channel)
#1001925 - Slack Bot! (nlbots.slack.com, #talkingbots)
#1001947 - Read Slack bot's feedback from disk (works)
#1001979 - Old Slack Slurper (sucks Slack channels & stores them)
#1001989 - List User Feedback Bot
#1001997 - Test loadVariableDefinition
#1002017 - Eleutheria Main, including Slack Bot (LIVE)
#1002278 - Eleutheria Main 2 (developing)
#1002578 - Eleu Core (Include)
#3000189 - Answer for stefanreich(>> t bla)
#3000190 - Answer for stefanreich(>> t 20 questions)
#3000195 - Answer for stefanreich (>> y)
#3000196 - Answer for stefanreich (>> y)
#3000197 - Answer for stefanreich (>> program data sizes)
#3000198 - Answer for stefanreich (>> program data sizes)
#3000199 - Answer for stefanreich (>> program data sizes)
#3000200 - Answer for stefanreich (>> program data sizes)
#3000201 - Answer for stefanreich (>> list all files)
#3000202 - Answer for stefanreich (>> T conversion bot)
#3000238 - Answer for stefanreich (>> t power bot)
#3000382 - Answer for ferdie (>> t = 1, f = 0)
#3000383 - Answer for funkoverflow (>> t=1, f=0 okay)