sS botUserName = "celestia"; sS logFile = "slack.log"; static new ThreadLocal<Bool> dedicated; static new ThreadLocal<Channel> channel; static new ThreadLocal<S> slackUserName; static int maxAnswerLength = 1000, shortenTo = 200; sbool actuallyPost = true; static int slackSpeed = 5000, slackTimeout = 20000; static int historySuckSize = 1; sbool checkReactions = false; static new L actionsAfterPost; static new L<Channel> channels; static class Channel { S channelName; // includes the site S channelID, token; boolean generalRelease, postAsUser, dedicated; 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 && checkReactions) { // 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; } slackUserName.set(userName); 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); print("dedicated: " + dedicated); main.dedicated.set(dedicated); main.channel.set(this); S a; try { a = callStaticAnswerMethod(q); } catch e { printStackTrace(e); a = "?" + getInnerMessage(e); } logStructure(logFile, ll(now(), channelID, channelName, msg.ts, msg.userName, a)); if (nempty(a)) { // 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, "dedicated", dedicated); //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 postAs = null; // main.postAs.get(); S username = or(postAs, botUserName); S botIcon = null; // main.botIcon.get(); S url = "https://slack.com/api/chat.postMessage"; Map postData = litmap( "token", token, "channel", channelID, "text", text, "username", username/*, "parse", "none", "link_names", "1"*/, "icon_url", botIcon); if (postAsUser && postAs == null) postData.put("as_user", "true"); printStructure(postData); S data = doPostWithTimeout(postData, url, slackTimeout); 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 boolean slackBot_inited; static void initSlackBot() { if (slackBot_inited) ret; slackBot_inited = true; Channel c; c = new Channel; c.channelName = "buildingcoolstuff, #bot"; c.channelID = "C4XAPALRG"; c.token = celestiaToken(); c.postAsUser = true; c.dedicated = true; channels.add(c); c = new Channel; c.channelName = "buildingcoolstuff, #general"; c.channelID = "C4XTX60LF"; c.token = celestiaToken(); c.postAsUser = true; c.dedicated = false; 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 /*|| actualBotUserNames.contains(msg.userName)*/; } static void slackBotLoop() { slackSetTimeout(slackTimeout); try { long lastPrinted = now(); while licensed { runAndClear(actionsAfterPost); 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!?"); } } static void sayInDedicated(S s) { for (Channel c : channels) pcall { if (c.dedicated) c.postAnswer(s); } } static void dediSay(S s) { sayInDedicated(s); }
Began life as a copy of #1002268
download show line numbers debug dex old transpilations
Travelled to 15 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, jtubtzbbkimh, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1007827 |
Snippet name: | Slack Bot Include (for Celestia) |
Eternal ID of this version: | #1007827/33 |
Text MD5: | 7a5a4cba0e3038ddebc9e7e1a8a062bd |
Author: | stefan |
Category: | nl bots |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2017-05-06 16:34:20 |
Source code size: | 7484 bytes / 242 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 517 / 902 |
Version history: | 32 change(s) |
Referenced in: | #1007829 - Celestia Slack Bot [LIVE] - can recognize greetings and say "hi!" #1008059 - Celestia Slack Bot 2 [LIVE, multiple modules] #1013776 - Slack Bot Include (for Electron) |