static int slackSpeed = 1000; static int historySuckSize = 10; static L<S> attnPrefixes = litlist("!", "bot"); static new L<Channel> channels; static class Channel { S teamName; S channelName; // includes the site S channelID; S tokenPath; boolean generalRelease, postAsUser; boolean firstTime = true; new Map<S, Map> userInfos; S lastQuestion, lastAnswer; S lastAnswerTimestamp; // only for posted in this session int lastAnswerScore; void tendTo() { 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) if (!isMyAnswer(msg)) { S q = msg.text; S userName = getUserName(msg.user); Map userInfo = getUserInfo(msg.user); if (userInfo != null && eq(true, userInfo.get("is_bot"))) { print("Skipping question from bot " + userName + ": " + quote(q)); continue; } q = q.trim(); 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 { originalLine.set(q); main.userName.set(userName); S dialogID = "slack-" + userName; main.dialogID.set(dialogID); main.slackTS.set(msg.ts); main.channelName.set(channelName); // attn yes/no? attn.set(false); pcall { if (attnPrefixes.contains(get(javaTok(q), 1))) { attn.set(true); q = join(subList(javaTok(q), 3)).trim(); } } S a = answer(q, generalRelease); if (a != null) { // slack snippets have some size limit - let's forget that /*if (l(a) > maxAnswerLength) a = "```" + a + "```";*/ // post long stuff to tinybrain instead if (l(a) > maxAnswerLength) { S title = "Answer for " + userName + " (>> " + q + ")"; S id = ntUpload("eleutheria-for-user", title, unSlackSnippet(a)); boolean isSnippet = a.contains("```"); a = shorten(a, shortenTo) + (isSnippet ? "```" : "") + "\nFull answer here: tinybrain.de/" + parseSnippetID(id); } 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, "answertime", answerTime, "answer", a, "user", userName); historyAdd(logEntry); } } } } } Map getUserInfo(S userID) { if (userID == null) ret null; Map userInfo = userInfos.get(userID); if (userInfo == null) pcall { userInfo = slackGetUserInfo(token, userID); userInfos.put(userID, userInfo); } ret userInfo; } S getUserName(S userID) { Map userInfo = getUserInfo(userID); ret userInfo == null ? null : (S) userInfo.get("name"); } // returns the slack timestamp S postAnswer(S text) { S username = botUserName; S url = "https://slack.com/api/chat.postMessage"; Map postData = litmap( "token", token, "channel", channelID, "text", text, "username", username/*, "parse", "none", "link_names", "1"*/); if (postAsUser) postData.put("as_user", "true"); printStructure(postData); S data = doPost(postData, url); Map map = jsonDecodeMap(data); lastAnswerTimestamp = (S) map.get("ts"); lastAnswerScore = 0; print("postAnswer: " + data); ret lastAnswerTimestamp; } S getNewQuestions_lastSeen; L<SlackMsg> getNewQuestions() { // Get historySuckSize on first call (history before bot start) // and 1000 thereafter (just grab anything new) int limit = getNewQuestions_lastSeen == null ? historySuckSize : 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); } if (firstTime) msgs = subList(msgs, i+1); } firstTime = false; ret msgs; } } // end of class Channel static void initSlackBot() { S relpToken = loadSecretTextFileMandatory("#1001889", "relp-slack-botstuff-token").trim(); S devChannelToken = devChannelToken(); S nlbotsToken = loadSecretTextFileMandatory("#1001925", "nlbots-slack-token").trim(); Channel c; c = new Channel; c.channelName = "devchannel, #general"; c.channelID = "C0H0Q0J3D"; c.token = devChannelToken; c.postAsUser = true; channels.add(c); c = new Channel; c.channelName = "devchannel, #talkingbots"; c.channelID = "C0H0SR40J"; c.token = devChannelToken; c.postAsUser = true; channels.add(c); c = new Channel; c.channelName = "devchannel, #programming"; c.channelID = "C0H0MG0F6"; c.token = devChannelToken; c.postAsUser = true; channels.add(c); c = new Channel; c.channelName = "devchannel, #c_sharp"; c.channelID = "C0H0T0EQ6"; c.token = devChannelToken; c.postAsUser = true; channels.add(c); c = new Channel; c.channelName = "nlbots, #general"; c.channelID = "C0FHTG6SY"; // nlbots, #general c.token = nlbotsToken; channels.add(c); /*c = new Channel; c.channelID = "C0G5GPEMR"; // nlbots, #talkingbots c.token = nlbotsToken; channels.add(c);*/ /*c = new Channel; c.channelID = "G0HLW98RY"; // devchannel, #bot_test c.token = devChannelToken; c.postAsUser = true; channels.add(c);*/ } static int indexOfLastBotAnswer(L<SlackMsg> msgs) { for (int i = l(msgs)-1; i >= 0; i--) if (isMyAnswer(msgs.get(i))) ret i; ret -1; } static boolean isMyAnswer(SlackMsg msg) { ret msg.botName != null || eq(msg.userName, actualBotUserName); } static void slackBotLoop() { try { long lastPrinted = now(); while true { for (Channel c : channels) pcall { c.tendTo(); } sleep(slackSpeed); if (now() > lastPrinted + 60*1000) { lastPrinted = now(); print("Slack loop still going. " + unixTime()); } } } finally { print("Slack bot loop exit!?"); } }
Began life as a copy of #1002268
download show line numbers debug dex old transpilations
Travelled to 13 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1002647 |
Snippet name: | Slack Bot Include (dynamic channels, dev.) |
Eternal ID of this version: | #1002647/1 |
Text MD5: | e9f858dde1b9910b4f7ca1c30443bccc |
Author: | stefan |
Category: | nl bots |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2016-02-09 20:42:57 |
Source code size: | 7765 bytes / 254 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 693 / 588 |
Referenced in: | #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) |