Uses 5614K of libraries. Click here for Pure Java version (25791L/192K).
1 | !7 |
2 | |
3 | module NLRulesBot extends DynTalkBot2<.ByServer> { |
4 | void init { |
5 | super.init(); |
6 | makeByServer = () -> new ByServer; |
7 | useAGIBlueForDropPunctuation = false; |
8 | preprocessAtSelfToMyName = false; |
9 | dropPunctuation = false; |
10 | print("Total rules in all guilds: " + totalRuleCount()); |
11 | } |
12 | |
13 | sclass Rule { |
14 | GlobalID globalID = aGlobalIDObj(); |
15 | S text, comment; |
16 | |
17 | *() {} *(S *text) {} |
18 | } |
19 | |
20 | S renderRule(Rule r) { |
21 | ret r == null ? null : appendNewLineIfNempty(r.comment) + "Rule " + r.globalID + ": " + r.text; |
22 | } |
23 | |
24 | class ByServer extends DynTalkBot2<NLRulesBot.ByServer>.ByServer { |
25 | new L<Rule> rules; |
26 | new Set<Long> priorityChannels; // where we always respond |
27 | bool enableMagicQuotes = true; |
28 | new Map<Long, S> topicByChannel; |
29 | |
30 | new SimpleFactStore factStore; |
31 | new StringClustersWithIDs synonyms; |
32 | |
33 | delegate Cluster to StringClustersWithIDs. |
34 | |
35 | void _doneLoading { synonyms.onChange(r change); } |
36 | |
37 | S processSimplifiedLine(S s, O... _) { |
38 | try answer super.processSimplifiedLine(s, _); |
39 | ret fullTrim(processSimplifiedLine2(s, _)); |
40 | } |
41 | |
42 | S processSimplifiedLine2(S s, O... _) { |
43 | lock myLock(); |
44 | |
45 | try answer factStore_cmds(factStore, s, authed(_)); |
46 | try answer philosophyBotWithFactStore_discordAnswer( |
47 | ai_weightChangeBot_theory(), factStore, s, _); |
48 | |
49 | long channelID = longPar channelID(_); |
50 | |
51 | new Matches m; |
52 | S s2 = dropMyPrefixOrNull(s); |
53 | if (s2 != null) s = s2; |
54 | else if (!contains(priorityChannels, channelID)) null; |
55 | optPar Message msg; |
56 | Message.Attachment attachment = msg == null ? null : first(msg.getAttachments()); |
57 | if (attachment != null) { |
58 | File file = prepareCacheProgramFile(guildID + "/" + attachment.getFileName()); |
59 | print("Downloading attachment: " + file); |
60 | deleteFile(file); |
61 | if (!attachment.download(file)) ret "Couldn't download attachment"; |
62 | LS lines = tlft(loadTextFile(file)); |
63 | print("First line: " + first(lines)); |
64 | if (!swic(first(lines), "rule ")) print("File does not contain rules"); |
65 | else { |
66 | new L<T3S> toImport; // (globalID, text, comment) |
67 | new LS comment; |
68 | for (S line : lines) { |
69 | LS l = regexpICFullMatch_groups("Rule ([a-z]+):(.*)$", line); |
70 | if (nempty(l)) { |
71 | toImport.add(t3(first(l), second(l), lines_rtrim(comment)); |
72 | comment.clear(); |
73 | } else |
74 | comment.add(line); |
75 | } |
76 | S found = "Found " + nRules(toImport) + " in attachment"; |
77 | |
78 | int oldCount = l(rules); |
79 | Map<GlobalID, Rule> existing = indexByField globalID(rules); |
80 | int updated = 0, added = 0; |
81 | for (T3S rule : toImport) { |
82 | GlobalID id = GlobalID(rule.a); |
83 | S text = trim(rule.b); |
84 | Rule r = existing.get(id); |
85 | if (r == null) { |
86 | r = new Rule(text); |
87 | r.globalID = id; |
88 | r.comment = rule.c; |
89 | rules.add(r); |
90 | existing.put(id, r); |
91 | ++added; |
92 | } else if (neq(r.text, text)) { |
93 | r.comment = rule.c; |
94 | r.text = text; |
95 | ++updated; |
96 | } |
97 | } |
98 | |
99 | if (added+updated > 0) change(); |
100 | ret found + ". Had: " + oldCount + ". Added: " + added + ". Updated: " + updated + ". New count: " + l(rules); |
101 | } |
102 | } |
103 | |
104 | // duplicated patterns go here |
105 | if "on * say *|on * or * say *|on keyword * and * say *|on keyword * and keyword * say *|on keyword * or * say *|on keyword * say *|on *, image-google X|on *, google X|when <*> comes online, say *|on bot keyword * and keyword *, say *|on conversation keyword * and *, say *|on topic * and *, say *|on topic * and keyword *, say *" { |
106 | Rule r = firstWhereIC(rules, text := s); |
107 | if (r != null) ret "Rule exists: " + r.globalID; |
108 | rules.add(r = new Rule(s)); |
109 | change(); |
110 | ret "Rule added with ID " + r.globalID; |
111 | } |
112 | |
113 | if "rules|rules as file" { |
114 | if (empty(rules)) ret "No rules defined yet"; |
115 | uploadFileInChannel(channelID, |
116 | toUtf8(lines(map(r -> renderRule(r), rules))), |
117 | genericTextFileName(), null, null); |
118 | null; |
119 | } |
120 | |
121 | if "delete rule *|delete rule with id *" { |
122 | try answer checkAuth(_); |
123 | Rule r = firstWhere(rules, globalID := GlobalID($1)); |
124 | if (r == null) ret "Rule " + $1 + " not found"; |
125 | rules.remove(r); |
126 | change(); |
127 | ret "Rule deleted, " + nRule(rules) + " remain"; |
128 | } |
129 | |
130 | if "delete all rules" { |
131 | appendToTextFile(programFile("backups.txt"), guildStructure()); |
132 | try answer checkAuth(_); |
133 | rules.clear(); |
134 | change(); |
135 | ret "All rules deleted"; |
136 | } |
137 | |
138 | if "priority channel on" { |
139 | try answer checkAuth(_); |
140 | priorityChannels.add(channelID); change(); |
141 | ret "OK"; |
142 | } |
143 | |
144 | if "priority channel off" { |
145 | try answer checkAuth(_); |
146 | priorityChannels.remove(channelID); change(); |
147 | ret "OK"; |
148 | } |
149 | |
150 | if "disable magic quotes" { |
151 | try answer checkAuth(_); |
152 | enableMagicQuotes = false; change(); |
153 | ret "OK"; |
154 | } |
155 | |
156 | if "add synonym ...|add synonyms ..." { |
157 | LS tok = unquoteAll(wordTokC(m.rest())); |
158 | printStruct(+tok); |
159 | if (l(tok) <= 1) ret "Give me at least 2 synonyms, dude"; |
160 | for (int i = 1; i < l(tok); i++) |
161 | synonyms.addPair(first(tok), tok.get(i)); |
162 | ret "OK: " + synonyms.clusterWith(first(tok)).synonyms; |
163 | } |
164 | |
165 | if "delete synonym cluster *" { |
166 | Cluster c = synonyms.clusterWith($1); |
167 | if (c == null) ret "Cluster not found"; |
168 | synonyms.remove(c); |
169 | ret "Synonym cluster deleted: " + c.synonyms; |
170 | } |
171 | |
172 | if "delete synonym * from cluster *" { |
173 | Cluster c = synonyms.clusterWith($1); |
174 | if (c == null) ret "Cluster not found"; |
175 | if (!c.synonyms.remove($1)) ret format("Synonym * not found in " + c.synonyms); |
176 | change(); |
177 | if (l(c.synonyms) <= 1) synonyms.remove(c); |
178 | ret "OK"; |
179 | } |
180 | |
181 | if "list synonyms" |
182 | ret or2(lines(map(synonyms.clusters, c -> join(" = ", c.synonyms))), "No synonyms defined"); |
183 | |
184 | if "list synonyms for *" { |
185 | Cl<Cluster> l = synonyms.searchForCluster($1); |
186 | ret or2(lines(map(l, c -> join(" = ", c.synonyms))), format("No synonyms found for *", $1)); |
187 | } |
188 | |
189 | if "print log" { |
190 | try answer checkAuth(_); |
191 | ret str(_actualPrintLog()); |
192 | } |
193 | |
194 | if "lottery print log" { |
195 | try answer checkAuth(_); |
196 | S mod = dm_findModule("#1025913/LotteryBot"); |
197 | if (mod == null) ret "Not loaded"; |
198 | ret mod + "\n" + str(dm_call(mod, '_actualPrintLog)); |
199 | } |
200 | |
201 | if "lottery enabled" { |
202 | try answer checkAuth(_); |
203 | ret str(dm_get(dm_findModule("#1025913/LotteryBot"), 'enabled)); |
204 | } |
205 | |
206 | if "lottery last error" { |
207 | try answer checkAuth(_); |
208 | ret str(dm_get(dm_findModule("#1025913/LotteryBot"), '_error)); |
209 | } |
210 | |
211 | if "lottery reload" { |
212 | try answer checkAuth(_); |
213 | dm_reloadModule(dm_findModule("#1025913/LotteryBot")); |
214 | ret "OK (maybe)"; |
215 | } |
216 | |
217 | if (eqic(s, "help")) |
218 | ret ltrim([[ |
219 | I execute simple English rules. |
220 | |
221 | Stuff to say. |
222 | @me `on "hello bot" say "who are you"` |
223 | @me `on keyword "time" say "it is always time for a tea"` |
224 | @me `on "what is X" google X` |
225 | @me `on "show me X" image-google X` |
226 | @me `on bot keyword "game" and keyword "sure" say "let's play tic tac toe"` |
227 | @me `on topic "weather" and "nice", say "so the sun is shining?"` |
228 | @me `when @user comes online, say "hello friend"` |
229 | @me `rules` / `rules as file` - list rules |
230 | @me `delete rule abcdefghijkl` - delete rule with ID |
231 | |
232 | @me `add synonyms thx "thank you" thanks` - add synonyms |
233 | @me `delete synonym cluster thx` - delete all synonyms of thx |
234 | @me `delete synonym thx from cluster thanks` - delete a single synonym |
235 | @me `list synonyms` / `list synonyms for "thank you"` |
236 | |
237 | @me `topic` - show current topic for channel (see: conversation keyword) |
238 | @me `forget the topic` / `forget <topic>` - forget current topic |
239 | |
240 | @me `priority channel on` - respond to every msg |
241 | @me `priority channel off` - respond to msgs with my name only |
242 | @me `masters` / `add master <username>` / `delete master <username>` |
243 | @me `help` / `support` / `source code` |
244 | @me `download` - download my brain for this guild |
245 | |
246 | To fill me with rules, just upload the file you got from `rules as file` to the channel. |
247 | |
248 | [Bot made by https://BotCompany.de] |
249 | ]]).replace("@me", atSelf()); |
250 | |
251 | // find rule to execute |
252 | |
253 | new LinkedHashSet<S> outs; |
254 | new LinkedHashSet<S> outs2; // higher priority (two matches) |
255 | |
256 | for (Rule r : rules) pcall { |
257 | // IMPORTANT: patterns are duplicated above |
258 | |
259 | if (matchX2("on conversation keyword * and *, say *|on topic * and *, say *|on topic * and keyword *, say *", r.text, m)) { |
260 | S in1 = $1, in2 = $2, out = $3; |
261 | if (findWithSynonyms(in1, s)) |
262 | if (put_trueIfChanged(topicByChannel, channelID, in1)) change(); |
263 | if (synonyms.eqicOrInSameCluster(topicByChannel.get(channelID), in1) |
264 | && findWithSynonyms(in2, s)) |
265 | outs2.add(rewrite(out, _)); |
266 | } |
267 | |
268 | if (match("on * say *", r.text, m)) { |
269 | S in = $1, out = $2; |
270 | if (matchWithSynonyms(in, s)) |
271 | outs.add(rewrite(out, _)); |
272 | } |
273 | |
274 | if (match("on * or * say *", r.text, m)) { |
275 | S in1 = $1, in2 = $2, out = $3; |
276 | if (matchWithSynonyms(in1, s) || matchWithSynonyms(in2, s)) |
277 | outs.add(rewrite(out, _)); |
278 | } |
279 | |
280 | if (match_vbar("on keyword * and * say *|on keyword * and keyword * say *", r.text, m)) { |
281 | S in1 = $1, in2 = $2, out = $3; |
282 | if (findWithSynonyms(in1, s) && findWithSynonyms(in2, s)) |
283 | outs2.add(rewrite(out, _)); |
284 | } |
285 | |
286 | if (match("on keyword * or * say *", r.text, m)) { |
287 | S in1 = $1, in2 = $2, out = $3; |
288 | if (findWithSynonyms(in1, s) || findWithSynonyms(in2, s)) |
289 | outs2.add(rewrite(out, _)); |
290 | } |
291 | |
292 | if (match("on bot keyword * and keyword *, say *", r.text, m)) { |
293 | ISaid lastSaid = lastSaidInChannel.get(channelID); |
294 | if (lastSaid != null) { |
295 | S in1 = $1, in2 = $2, out = $3; |
296 | if (findWithSynonyms(in1, lastSaid.text) && findWithSynonyms(in2, s)) |
297 | outs2.add(rewrite(out, _)); |
298 | } |
299 | } |
300 | |
301 | if (match("on keyword * say *", r.text, m)) { |
302 | S in = $1, out = $2; |
303 | if (findWithSynonyms(in, s)) |
304 | outs.add(rewrite(out, _)); |
305 | } |
306 | |
307 | if (match("on *, image-google X", r.text, m)) |
308 | if (flexMatchIC(replaceWord($1, "X", "*"), s, m)) { |
309 | bool nsfw = discord_isNSFWChannel_gen(optPar channel(_)); |
310 | print(+nsfw); |
311 | BufferedImage img = nsfw ? quickVisualize_nsfw($1) : quickVisualize($1); |
312 | if (img == null) ret "No image found, sorry!"; |
313 | postImage(paramsToMap(_), uploadToImageServer(img, $1)); |
314 | // null; |
315 | } |
316 | |
317 | if (match("on *, google X", r.text, m)) |
318 | if (flexMatchIC(replaceWord($1, "X", "*"), s, m)) { |
319 | bool nsfw = discord_isNSFWChannel_gen(optPar channel(_)); |
320 | print(+nsfw); |
321 | ret discord_google($1, results := 1, safeSearch := !nsfw); |
322 | } |
323 | } |
324 | |
325 | if "topic|conversation keyword" { |
326 | S topic = topicByChannel.get(channelID); |
327 | ret nempty(topic) ? "Current topic in this channel is: " + topic : "No topic set in this channel"; |
328 | } |
329 | |
330 | if "forget topic|forget the topic" |
331 | ret forgetTopic(channelID); |
332 | |
333 | if "forget ..." |
334 | if (synonyms.eqicOrInSameCluster($1, topicByChannel.get(channelID))) |
335 | ret forgetTopic(channelID); |
336 | |
337 | if "download" { |
338 | optPar Guild guild; |
339 | optPar MessageChannel channel; |
340 | ret serveGuildBackup(channel, guild, |
341 | structure_nullingInstancesOfClass(_getClass(module()), ByServer.this)); |
342 | } |
343 | |
344 | if "stats" |
345 | ret "I have " + nRules(rules) + " and know " + n2(synonyms.totalCount(), "synonym"); |
346 | |
347 | if "masters" |
348 | ret renderMasters(); |
349 | |
350 | try answer random(outs2); |
351 | try answer random(outs); |
352 | |
353 | if (enableMagicQuotes) |
354 | try answer answer_magicQuoteInput(s, useTranspiled := true); |
355 | null; |
356 | } |
357 | |
358 | S rewrite(S answer, O... _) { |
359 | if (containsIC(answer, "<user>")) { |
360 | optPar S name; |
361 | if (empty(name)) name = discord_optParUserName(_); |
362 | if (nempty(name)) |
363 | answer = replaceIC(answer, "<user>", name); |
364 | } |
365 | ret answer; |
366 | } |
367 | |
368 | void onOnlineStatusChange(UserUpdateOnlineStatusEvent e) { |
369 | User user = e.getMember().getUser(); |
370 | print("User status change: " + user + " " + e.getOldValue() + " -> " + e.getNewValue()); |
371 | |
372 | // only react to user coming online |
373 | if (e.getOldValue() != OnlineStatus.OFFLINE) ret; |
374 | |
375 | long id = user.getIdLong(); |
376 | |
377 | new LinkedHashSet<S> outs; |
378 | new Matches m; |
379 | |
380 | for (Rule r : rules) pcall { |
381 | // IMPORTANT: patterns are duplicated above |
382 | |
383 | if (match("when <*> comes online, say *", r.text, m)) { |
384 | long _id = parseLongOpt($1); |
385 | S out = $2; |
386 | if (id == _id) |
387 | outs.add(rewrite(out, name := user.getName())); |
388 | } |
389 | } |
390 | |
391 | postInChannel(preferredChannelID, random(outs)); |
392 | } |
393 | |
394 | bool findWithSynonyms(S pat, S s) { |
395 | for (S pat2 : splitAtComma(pat)) |
396 | if (!findWithSynonyms2(pat2, s)) |
397 | false; |
398 | true; |
399 | } |
400 | |
401 | bool findWithSynonyms2(S pat2, S s) { |
402 | for (S pat3 : synonyms.extend(pat2)) |
403 | if (find3(pat3, s)) |
404 | true; |
405 | false; |
406 | } |
407 | |
408 | bool matchWithSynonyms(S pat, S s) { |
409 | for (S pat2 : synonyms.extend(pat)) |
410 | if (match(pat2, s)) |
411 | true; |
412 | false; |
413 | } |
414 | |
415 | S forgetTopic(long channelID) { |
416 | topicByChannel.remove(channelID); |
417 | ret "OK, let's talk about something else"; |
418 | } |
419 | } |
420 | |
421 | long totalRuleCount() { |
422 | ret intSumAsLong(map(syncCloneValues(dataByServer), bs -> l(bs.rules))); |
423 | } |
424 | |
425 | S renderMasters() { |
426 | ret empty(authorizedUsers) ? "I have no masters." : "My masters are: " + ai_renderAndList(map discordAt(authorizedUsers)); |
427 | } |
428 | |
429 | LS splitIntoLines(S s) { |
430 | ret tlft_honoringTokens(s); |
431 | } |
432 | } |
Began life as a copy of #1025348
download show line numbers debug dex old transpilations
Travelled to 9 computer(s): bhatertpkbcr, dbzfplsxganw, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
Snippet ID: | #1025624 |
Snippet name: | Katherine |
Eternal ID of this version: | #1025624/61 |
Text MD5: | 0fff40582a324b98cebeeb99b5e135ac |
Transpilation MD5: | 5639ade72e9f1c03d6135efee16bfcb4 |
Author: | stefan |
Category: | javax |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2020-08-13 16:27:18 |
Source code size: | 15129 bytes / 432 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 521 / 68295 |
Version history: | 60 change(s) |
Referenced in: | [show references] |