1 | sS botUserName = "celestia"; |
2 | sS logFile = "slack.log"; |
3 | |
4 | static new ThreadLocal<Bool> dedicated; |
5 | static new ThreadLocal<Channel> channel; |
6 | static new ThreadLocal<S> slackUserName; |
7 | |
8 | static int maxAnswerLength = 1000, shortenTo = 200; |
9 | |
10 | sbool actuallyPost = true; |
11 | static int slackSpeed = 5000, slackTimeout = 20000; |
12 | |
13 | static int historySuckSize = 1; |
14 | sbool checkReactions = false; |
15 | |
16 | static new L actionsAfterPost; |
17 | |
18 | static new L<Channel> channels; |
19 | |
20 | static class Channel { |
21 | S channelName; // includes the site |
22 | S channelID, token; |
23 | boolean generalRelease, postAsUser, dedicated; |
24 | |
25 | boolean firstTime = true; |
26 | |
27 | new Map<S, Map> userInfos; |
28 | |
29 | S lastQuestion, lastAnswer; |
30 | S lastAnswerTimestamp; // only for posted in this session |
31 | int lastAnswerScore; |
32 | |
33 | void tendTo() { |
34 | if (lastAnswerTimestamp != null && checkReactions) { |
35 | // Poll reactions to our last answer - is there a more efficient way? |
36 | int score = slackReactionScore(token, channelID, lastAnswerTimestamp); |
37 | if (score != lastAnswerScore) { |
38 | print("Reaction score changes to " + score); |
39 | //recordFeedback(lastQuestion, lastAnswer, lastAnswerTimestamp, "emoji", score-lastAnswerScore); |
40 | lastAnswerScore = score; |
41 | } |
42 | } |
43 | |
44 | L<SlackMsg> qs = getNewQuestions(); |
45 | //print(l(qs) + " question(s)."); |
46 | //printList(qs); |
47 | for (SlackMsg msg : qs) if (!isMyAnswer(msg)) { |
48 | S q = msg.text; |
49 | S userName = getUserName(msg.user); |
50 | Map userInfo = getUserInfo(msg.user); |
51 | if (userInfo != null && eq(true, userInfo.get("is_bot"))) { |
52 | print("Skipping question from bot " + userName + ": " + quote(q)); |
53 | continue; |
54 | } |
55 | |
56 | slackUserName.set(userName); |
57 | |
58 | q = q.trim(); |
59 | print("Processing question: " + quote(q)); |
60 | if (lastAnswer != null) { // TODO: maybe check the time delay |
61 | int score = feedbackScore(q); |
62 | if (score != 0) { |
63 | //recordFeedback(lastQuestion /*lastPostByUser.get(userName)*/, lastAnswer, lastAnswerTimestamp, q, score); |
64 | if (score > 0) |
65 | pcall { slackSmile(token, channelID, msg.ts); } |
66 | else |
67 | pcall { slackReact(token, channelID, msg.ts, "thinking_face"); } |
68 | } |
69 | lastAnswer = null; |
70 | } |
71 | //if (userName != null) lastPostByUser.put(userName, msg.text); |
72 | print("dedicated: " + dedicated); |
73 | main.dedicated.set(dedicated); |
74 | main.channel.set(this); |
75 | S a; |
76 | try { |
77 | a = callStaticAnswerMethod(q); |
78 | } catch e { |
79 | printStackTrace(e); |
80 | a = "?" + getInnerMessage(e); |
81 | } |
82 | logStructure(logFile, ll(now(), channelID, channelName, msg.ts, msg.userName, a)); |
83 | if (nempty(a)) { |
84 | // slack snippets have some size limit - let's forget that |
85 | /*if (l(a) > maxAnswerLength) |
86 | a = "```" + a + "```";*/ |
87 | |
88 | // post long stuff to tinybrain instead |
89 | if (l(a) > maxAnswerLength) { |
90 | S title = "Answer for " + userName + " (>> " + q + ")"; |
91 | S id = ntUpload("eleutheria-for-user", title, unSlackSnippet(a)); |
92 | boolean isSnippet = a.contains("```"); |
93 | a = shorten(a, shortenTo) + (isSnippet ? "```" : "") + "\nFull answer here: tinybrain.de/" + parseSnippetID(id); |
94 | } |
95 | if (userName != null) |
96 | a = "<@" + userName + "> " + a; |
97 | print("Answering with: " + quote(a)); |
98 | lastAnswer = a; |
99 | lastQuestion = q; |
100 | if (actuallyPost) { |
101 | S answerTime = postAnswer(a); |
102 | Map logEntry = litmap("question", q, "answertime", answerTime, "answer", a, "user", userName, "dedicated", dedicated); |
103 | //historyAdd(logEntry); |
104 | } |
105 | } |
106 | } |
107 | } |
108 | |
109 | Map getUserInfo(S userID) { |
110 | if (userID == null) ret null; |
111 | Map userInfo = userInfos.get(userID); |
112 | if (userInfo == null) pcall { |
113 | userInfo = slackGetUserInfo(token, userID); |
114 | userInfos.put(userID, userInfo); |
115 | } |
116 | ret userInfo; |
117 | } |
118 | |
119 | S getUserName(S userID) { |
120 | Map userInfo = getUserInfo(userID); |
121 | ret userInfo == null ? null : (S) userInfo.get("name"); |
122 | } |
123 | |
124 | // returns the slack timestamp |
125 | S postAnswer(S text) { |
126 | S postAs = null; // main.postAs.get(); |
127 | S username = or(postAs, botUserName); |
128 | S botIcon = null; // main.botIcon.get(); |
129 | S url = "https://slack.com/api/chat.postMessage"; |
130 | Map postData = litmap( |
131 | "token", token, |
132 | "channel", channelID, |
133 | "text", text, |
134 | "username", username/*, |
135 | "parse", "none", |
136 | "link_names", "1"*/, |
137 | "icon_url", botIcon); |
138 | if (postAsUser && postAs == null) |
139 | postData.put("as_user", "true"); |
140 | printStructure(postData); |
141 | S data = doPostWithTimeout(postData, url, slackTimeout); |
142 | Map map = jsonDecodeMap(data); |
143 | lastAnswerTimestamp = (S) map.get("ts"); |
144 | lastAnswerScore = 0; |
145 | print("postAnswer: " + data); |
146 | ret lastAnswerTimestamp; |
147 | } |
148 | |
149 | S getNewQuestions_lastSeen; |
150 | |
151 | L<SlackMsg> getNewQuestions() { |
152 | // Get historySuckSize on first call (history before bot start) |
153 | // and 1000 thereafter (just grab anything new) |
154 | int limit = getNewQuestions_lastSeen == null ? historySuckSize : 1000; |
155 | L<SlackMsg> msgs = slackReadChannel(channelID, token, limit, getNewQuestions_lastSeen); |
156 | if (!msgs.isEmpty()) getNewQuestions_lastSeen = last(msgs).ts; |
157 | int i = indexOfLastBotAnswer(msgs); |
158 | if (i >= 0) { |
159 | print("Last bot answer at " + (i+1) + "/" + l(msgs) + ": " + structure(msgs.get(i))); |
160 | |
161 | // update variables |
162 | lastAnswer = msgs.get(i).text; |
163 | for (int j = 0; j < i; j++) { |
164 | SlackMsg msg = msgs.get(j); |
165 | S userName = getUserName(msg.user); |
166 | //if (userName != null) lastPostByUser.put(userName, msg.text); |
167 | } |
168 | |
169 | if (firstTime) |
170 | msgs = subList(msgs, i+1); |
171 | } |
172 | firstTime = false; |
173 | ret msgs; |
174 | } |
175 | |
176 | } // end of class Channel |
177 | |
178 | static boolean slackBot_inited; |
179 | |
180 | static void initSlackBot() { |
181 | if (slackBot_inited) ret; |
182 | slackBot_inited = true; |
183 | |
184 | Channel c; |
185 | |
186 | c = new Channel; |
187 | c.channelName = "buildingcoolstuff, #bot"; |
188 | c.channelID = "C4XAPALRG"; |
189 | c.token = celestiaToken(); |
190 | c.postAsUser = true; |
191 | c.dedicated = true; |
192 | channels.add(c); |
193 | |
194 | c = new Channel; |
195 | c.channelName = "buildingcoolstuff, #general"; |
196 | c.channelID = "C4XTX60LF"; |
197 | c.token = celestiaToken(); |
198 | c.postAsUser = true; |
199 | c.dedicated = false; |
200 | channels.add(c); |
201 | } |
202 | |
203 | static int indexOfLastBotAnswer(L<SlackMsg> msgs) { |
204 | for (int i = l(msgs)-1; i >= 0; i--) |
205 | if (isMyAnswer(msgs.get(i))) ret i; |
206 | ret -1; |
207 | } |
208 | |
209 | static boolean isMyAnswer(SlackMsg msg) { |
210 | ret msg.botName != null /*|| actualBotUserNames.contains(msg.userName)*/; |
211 | } |
212 | |
213 | static void slackBotLoop() { |
214 | slackSetTimeout(slackTimeout); |
215 | try { |
216 | long lastPrinted = now(); |
217 | while licensed { |
218 | runAndClear(actionsAfterPost); |
219 | for (Channel c : channels) pcall { |
220 | c.tendTo(); |
221 | } |
222 | sleep(slackSpeed); |
223 | if (now() > lastPrinted + 60*1000) { |
224 | lastPrinted = now(); |
225 | print("Slack loop still going. " + unixTime()); |
226 | } |
227 | } |
228 | } finally { |
229 | print("Slack bot loop exit!?"); |
230 | } |
231 | } |
232 | |
233 | static void sayInDedicated(S s) { |
234 | for (Channel c : channels) pcall { |
235 | if (c.dedicated) |
236 | c.postAnswer(s); |
237 | } |
238 | } |
239 | |
240 | static void dediSay(S s) { |
241 | sayInDedicated(s); |
242 | } |
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: | 516 / 902 |
Version history: | 32 change(s) |
Referenced in: | [show references] |