1 | static int slackSpeed = 500, slackTimeout = 20000; |
2 | |
3 | static int historySuckSize = 10; |
4 | |
5 | static L<S> attnPrefixes = litlist("!" /*, "eleutheria", "eleu"*/); |
6 | |
7 | static new L<Channel> channels; |
8 | |
9 | static class Channel { |
10 | S channelName; // includes the site |
11 | S channelID, token; |
12 | boolean generalRelease, postAsUser, dedicated; |
13 | |
14 | boolean firstTime = true; |
15 | |
16 | new Map<S, Map> userInfos; |
17 | |
18 | S lastQuestion, lastAnswer; |
19 | S lastAnswerTimestamp; // only for posted in this session |
20 | int lastAnswerScore; |
21 | |
22 | void tendTo() { |
23 | if (lastAnswerTimestamp != null) { |
24 | // Poll reactions to our last answer - is there a more efficient way? |
25 | int score = slackReactionScore(token, channelID, lastAnswerTimestamp); |
26 | if (score != lastAnswerScore) { |
27 | print("Reaction score changes to " + score); |
28 | recordFeedback(lastQuestion, lastAnswer, lastAnswerTimestamp, "emoji", score-lastAnswerScore); |
29 | lastAnswerScore = score; |
30 | } |
31 | } |
32 | |
33 | L<SlackMsg> qs = getNewQuestions(); |
34 | //print(l(qs) + " question(s)."); |
35 | //printList(qs); |
36 | for (SlackMsg msg : qs) if (!isMyAnswer(msg)) { |
37 | S q = msg.text; |
38 | S userName = getUserName(msg.user); |
39 | Map userInfo = getUserInfo(msg.user); |
40 | if (userInfo != null && eq(true, userInfo.get("is_bot"))) { |
41 | print("Skipping question from bot " + userName + ": " + quote(q)); |
42 | continue; |
43 | } |
44 | |
45 | q = q.trim(); |
46 | print("Processing question: " + quote(q)); |
47 | if (lastAnswer != null) { // TODO: maybe check the time delay |
48 | int score = feedbackScore(q); |
49 | if (score != 0) { |
50 | recordFeedback(lastQuestion /*lastPostByUser.get(userName)*/, lastAnswer, lastAnswerTimestamp, q, score); |
51 | if (score > 0) |
52 | pcall { slackSmile(token, channelID, msg.ts); } |
53 | else |
54 | pcall { slackReact(token, channelID, msg.ts, "thinking_face"); } |
55 | } |
56 | lastAnswer = null; |
57 | } |
58 | //if (userName != null) lastPostByUser.put(userName, msg.text); |
59 | pcall { |
60 | originalLine.set(q); |
61 | main.userName.set(userName); |
62 | S dialogID = "slack-" + userName; |
63 | main.dialogID.set(dialogID); |
64 | main.slackTS.set(msg.ts); |
65 | main.channelName.set(channelName); |
66 | |
67 | // attn yes/no? |
68 | |
69 | main.dedicated.set(dedicated); |
70 | attn.set(dedicated); |
71 | pcall { |
72 | if (!dedicated && attnPrefixes.contains(get(javaTok(q), 1))) { |
73 | attn.set(true); |
74 | q = join(subList(javaTok(q), 3)).trim(); |
75 | } |
76 | } |
77 | |
78 | S a = answer(q, generalRelease); |
79 | if (a != null) { |
80 | // slack snippets have some size limit - let's forget that |
81 | /*if (l(a) > maxAnswerLength) |
82 | a = "```" + a + "```";*/ |
83 | |
84 | // post long stuff to tinybrain instead |
85 | if (l(a) > maxAnswerLength) { |
86 | S title = "Answer for " + userName + " (>> " + q + ")"; |
87 | S id = ntUpload("eleutheria-for-user", title, unSlackSnippet(a)); |
88 | boolean isSnippet = a.contains("```"); |
89 | a = shorten(a, shortenTo) + (isSnippet ? "```" : "") + "\nFull answer here: tinybrain.de/" + parseSnippetID(id); |
90 | } |
91 | if (userName != null) |
92 | a = "<@" + userName + "> " + a; |
93 | print("Answering with: " + quote(a)); |
94 | lastAnswer = a; |
95 | lastQuestion = q; |
96 | if (actuallyPost) { |
97 | S answerTime = postAnswer(a); |
98 | Map logEntry = litmap("question", q, "answertime", answerTime, "answer", a, "user", userName, "dedicated", dedicated); |
99 | historyAdd(logEntry); |
100 | } |
101 | } |
102 | } |
103 | } |
104 | } |
105 | |
106 | Map getUserInfo(S userID) { |
107 | if (userID == null) ret null; |
108 | Map userInfo = userInfos.get(userID); |
109 | if (userInfo == null) pcall { |
110 | userInfo = slackGetUserInfo(token, userID); |
111 | userInfos.put(userID, userInfo); |
112 | } |
113 | ret userInfo; |
114 | } |
115 | |
116 | S getUserName(S userID) { |
117 | Map userInfo = getUserInfo(userID); |
118 | ret userInfo == null ? null : (S) userInfo.get("name"); |
119 | } |
120 | |
121 | // returns the slack timestamp |
122 | S postAnswer(S text) { |
123 | S postAs = main.postAs.get(); |
124 | S username = or(postAs, botUserName); |
125 | S botIcon = main.botIcon.get(); |
126 | S url = "https://slack.com/api/chat.postMessage"; |
127 | Map postData = litmap( |
128 | "token", token, |
129 | "channel", channelID, |
130 | "text", text, |
131 | "username", username/*, |
132 | "parse", "none", |
133 | "link_names", "1"*/, |
134 | "icon_url", botIcon); |
135 | if (postAsUser && postAs == null) |
136 | postData.put("as_user", "true"); |
137 | printStructure(postData); |
138 | S data = doPostWithTimeout(postData, url, slackTimeout); |
139 | Map map = jsonDecodeMap(data); |
140 | lastAnswerTimestamp = (S) map.get("ts"); |
141 | lastAnswerScore = 0; |
142 | print("postAnswer: " + data); |
143 | ret lastAnswerTimestamp; |
144 | } |
145 | |
146 | S getNewQuestions_lastSeen; |
147 | |
148 | L<SlackMsg> getNewQuestions() { |
149 | // Get historySuckSize on first call (history before bot start) |
150 | // and 1000 thereafter (just grab anything new) |
151 | int limit = getNewQuestions_lastSeen == null ? historySuckSize : 1000; |
152 | L<SlackMsg> msgs = slackReadChannel(channelID, token, limit, getNewQuestions_lastSeen); |
153 | if (!msgs.isEmpty()) getNewQuestions_lastSeen = last(msgs).ts; |
154 | int i = indexOfLastBotAnswer(msgs); |
155 | if (i >= 0) { |
156 | print("Last bot answer at " + (i+1) + "/" + l(msgs) + ": " + structure(msgs.get(i))); |
157 | |
158 | // update variables |
159 | lastAnswer = msgs.get(i).text; |
160 | for (int j = 0; j < i; j++) { |
161 | SlackMsg msg = msgs.get(j); |
162 | S userName = getUserName(msg.user); |
163 | //if (userName != null) lastPostByUser.put(userName, msg.text); |
164 | } |
165 | |
166 | if (firstTime) |
167 | msgs = subList(msgs, i+1); |
168 | } |
169 | firstTime = false; |
170 | ret msgs; |
171 | } |
172 | |
173 | } // end of class Channel |
174 | |
175 | static boolean slackBot_inited; |
176 | |
177 | static void initSlackBot() { |
178 | if (slackBot_inited) ret; |
179 | slackBot_inited = true; |
180 | |
181 | //S devChannelToken = devChannelToken(); |
182 | S nlbotsToken = loadSecretTextFile("#1001925", "nlbots-slack-token").trim(); |
183 | S eleuToken = loadSecretTextFile("#1001925", "eleu-slack-token").trim(); |
184 | S devchattesttoken = loadSecretTextFile("#1001925", "devchat-test-token").trim(); |
185 | |
186 | Channel c; |
187 | |
188 | /*c = new Channel; |
189 | c.channelName = "devchannel, #talkingbots"; |
190 | c.channelID = "C0H0SR40J"; |
191 | c.token = devChannelToken; |
192 | c.postAsUser = true; |
193 | channels.add(c); |
194 | |
195 | c = new Channel; |
196 | c.channelName = "devchannel, #eleudedi"; |
197 | c.channelID = "C0MDDQEJY"; |
198 | c.token = devChannelToken; |
199 | c.postAsUser = true; |
200 | c.dedicated = true; |
201 | channels.add(c);*/ |
202 | |
203 | /*c = new Channel; |
204 | c.channelName = "eleu, #general"; |
205 | c.channelID = "C0MLVC346"; |
206 | c.token = eleuToken; |
207 | c.postAsUser = true; |
208 | c.dedicated = true; |
209 | channels.add(c);*/ |
210 | |
211 | /*c = new Channel; |
212 | c.channelName = "devchannel, #general"; |
213 | c.channelID = "C0H0Q0J3D"; |
214 | c.token = devChannelToken; |
215 | c.postAsUser = true; |
216 | channels.add(c);*/ |
217 | |
218 | /*c = new Channel; |
219 | c.channelName = "devchannel, #programming"; |
220 | c.channelID = "C0H0MG0F6"; |
221 | c.token = devChannelToken; |
222 | c.postAsUser = true; |
223 | channels.add(c);*/ |
224 | |
225 | /*c = new Channel; |
226 | c.channelName = "devchannel, #c_sharp"; |
227 | c.channelID = "C0H0T0EQ6"; |
228 | c.token = devChannelToken; |
229 | c.postAsUser = true; |
230 | channels.add(c);*/ |
231 | |
232 | c = new Channel; |
233 | c.channelName = "nlbots, #general"; |
234 | c.channelID = "C0FHTG6SY"; // nlbots, #general |
235 | c.token = nlbotsToken; |
236 | channels.add(c); |
237 | |
238 | /*c = new Channel; |
239 | c.channelID = "C0G5GPEMR"; // nlbots, #talkingbots |
240 | c.token = nlbotsToken; |
241 | |
242 | channels.add(c);*/ |
243 | |
244 | /*c = new Channel; |
245 | c.channelID = "G0HLW98RY"; // devchannel, #bot_test |
246 | c.token = devChannelToken; |
247 | c.postAsUser = true; |
248 | channels.add(c);*/ |
249 | |
250 | c = new Channel; |
251 | c.channelName = "devchat-test, #general"; |
252 | c.channelID = "C0QK003PV"; |
253 | c.token = devchattesttoken; |
254 | c.postAsUser = true; |
255 | channels.add(c); |
256 | } |
257 | |
258 | static int indexOfLastBotAnswer(L<SlackMsg> msgs) { |
259 | for (int i = l(msgs)-1; i >= 0; i--) |
260 | if (isMyAnswer(msgs.get(i))) ret i; |
261 | ret -1; |
262 | } |
263 | |
264 | static boolean isMyAnswer(SlackMsg msg) { |
265 | ret msg.botName != null || actualBotUserNames.contains(msg.userName); |
266 | } |
267 | |
268 | static void slackBotLoop() { |
269 | slackSetTimeout(slackTimeout); |
270 | try { |
271 | long lastPrinted = now(); |
272 | while true { |
273 | for (Channel c : channels) pcall { |
274 | c.tendTo(); |
275 | } |
276 | sleep(slackSpeed); |
277 | if (now() > lastPrinted + 60*1000) { |
278 | lastPrinted = now(); |
279 | print("Slack loop still going. " + unixTime()); |
280 | } |
281 | } |
282 | } finally { |
283 | print("Slack bot loop exit!?"); |
284 | } |
285 | } |
286 | |
287 | static void sayInDedicated(S s) { |
288 | for (Channel c : channels) pcall { |
289 | if (c.dedicated) |
290 | c.postAnswer(s); |
291 | } |
292 | } |
293 | |
294 | static void dediSay(S s) { |
295 | sayInDedicated(s); |
296 | } |
download show line numbers debug dex old transpilations
Travelled to 14 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1002268 |
Snippet name: | Slack Bot Include |
Eternal ID of this version: | #1002268/1 |
Text MD5: | fcc692700718522ace77c8a0e9c596b6 |
Author: | stefan |
Category: | nl bots |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2016-05-05 13:56:13 |
Source code size: | 8976 bytes / 296 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 886 / 1503 |
Referenced in: | [show references] |