Uses 6525K of libraries. Click here for Pure Java version (23606L/135K).
1 | !7 |
2 | |
3 | cmodule 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; |
16 | |
17 | *() {} *(S *text) {} |
18 | } |
19 | |
20 | S renderRule(Rule r) { |
21 | ret r == null ? null : "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 | |
29 | new SimpleFactStore factStore; |
30 | new StringClustersWithIDs synonyms; |
31 | |
32 | delegate Cluster to StringClustersWithIDs. |
33 | |
34 | void _doneLoading { synonyms.onChange(r change); } |
35 | |
36 | synchronized S processSimplifiedLine(S s, O... _) { |
37 | try answer super.processSimplifiedLine(s, _); |
38 | |
39 | try answer factStore_cmds(factStore, s, authed(_)); |
40 | try answer philosophyBotWithFactStore_discordAnswer( |
41 | ai_weightChangeBot_theory(), factStore, s, _); |
42 | |
43 | long channelID = longPar channelID(_); |
44 | |
45 | new Matches m; |
46 | S s2 = dropMyPrefixOrNull(s); |
47 | if (s2 != null) s = s2; |
48 | else if (!contains(priorityChannels, channelID)) null; |
49 | optPar Message msg; |
50 | Message.Attachment attachment = msg == null ? null : first(msg.getAttachments()); |
51 | if (attachment != null) { |
52 | long guildID = idForByServer(this); |
53 | File file = prepareCacheProgramFile(guildID + "/" + attachment.getFileName()); |
54 | print("Downloading attachment: " + file); |
55 | deleteFile(file); |
56 | if (!attachment.download(file)) ret "Couldn't download attachment"; |
57 | LS lines = tlft(loadTextFile(file)); |
58 | print("First line: " + first(lines)); |
59 | if (!swic(first(lines), "rule ")) print("Ignoring attachment"); |
60 | else { |
61 | new LPairS toImport; // (globalID, text) |
62 | for (S line : lines) { |
63 | LS l = regexpICFullMatch_groups("Rule ([a-z]+):(.*)$", line); |
64 | if (nempty(l)) |
65 | toImport.add(listToPair(l)); |
66 | } |
67 | S found = "Found " + nRules(toImport) + " in attachment"; |
68 | |
69 | int oldCount = l(rules); |
70 | Map<GlobalID, Rule> existing = indexByField globalID(rules); |
71 | int updated = 0, added = 0; |
72 | for (PairS rule : toImport) { |
73 | GlobalID id = GlobalID(rule.a); |
74 | S text = trim(rule.b); |
75 | Rule r = existing.get(id); |
76 | if (r == null) { |
77 | r = new Rule(text); |
78 | r.globalID = id; |
79 | rules.add(r); |
80 | existing.put(id, r); |
81 | ++added; |
82 | } else if (neq(r.text, text)) { |
83 | r.text = text; |
84 | ++updated; |
85 | } |
86 | } |
87 | |
88 | if (added+updated > 0) change(); |
89 | ret found + ". Had: " + oldCount + ". Added: " + added + ". Updated: " + updated + ". New count: " + l(rules); |
90 | } |
91 | } |
92 | |
93 | // duplicated patterns go here |
94 | if "on * say *|on * or * say *|on keyword * and * 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 *" { |
95 | Rule r = firstWhereIC(rules, text := s); |
96 | if (r != null) ret "Rule exists: " + r.globalID; |
97 | rules.add(r = new Rule(s)); |
98 | change(); |
99 | ret "Rule added with ID " + r.globalID; |
100 | } |
101 | |
102 | if "rules" |
103 | ret or2(lines(map(r -> renderRule(r), rules)), "No rules defined yet"); |
104 | |
105 | if "rules as file" { |
106 | uploadFileInChannel(channelID, |
107 | toUtf8(lines(map(r -> renderRule(r), rules))), |
108 | genericTextFileName(), null, null); |
109 | null; |
110 | } |
111 | |
112 | if "delete rule *|delete rule with id *" { |
113 | try answer checkAuth(_); |
114 | Rule r = firstWhere(rules, globalID := GlobalID($1)); |
115 | if (r == null) ret "Rule " + $1 + " not found"; |
116 | rules.remove(r); |
117 | change(); |
118 | ret "Rule deleted, " + nRule(rules) + " remain"; |
119 | } |
120 | |
121 | if "delete all rules" { |
122 | appendToTextFile(programFile("backups.txt"), guildStructure()); |
123 | try answer checkAuth(_); |
124 | rules.clear(); |
125 | change(); |
126 | ret "All rules deleted"; |
127 | } |
128 | |
129 | if "priority channel on" { |
130 | try answer checkAuth(_); |
131 | priorityChannels.add(channelID); change(); |
132 | ret "OK"; |
133 | } |
134 | |
135 | if "priority channel off" { |
136 | try answer checkAuth(_); |
137 | priorityChannels.remove(channelID); change(); |
138 | ret "OK"; |
139 | } |
140 | |
141 | if "disable magic quotes" { |
142 | try answer checkAuth(_); |
143 | enableMagicQuotes = false; change(); |
144 | ret "OK"; |
145 | } |
146 | |
147 | if "add synonym ...|add synonyms ..." { |
148 | LS tok = unquoteAll(wordTokC(m.rest())); |
149 | if (l(tok) <= 1) ret "Give me at least 2 synonyms, dude"; |
150 | for (int i = 1; i < l(tok); i++) |
151 | synonyms.addPair(first(tok), tok.get(i)); |
152 | ret "OK: " + synonyms.clusterWith(first(tok)).synonyms; |
153 | } |
154 | |
155 | if "delete synonym cluster *" { |
156 | Cluster c = synonyms.clusterWith($1); |
157 | if (c == null) ret "Cluster not found"; |
158 | synonyms.remove(c); |
159 | ret "Synonym cluster deleted: " + c.synonyms; |
160 | } |
161 | |
162 | if "delete synonym * from cluster *" { |
163 | Cluster c = synonyms.clusterWith($1); |
164 | if (c == null) ret "Cluster not found"; |
165 | if (!c.synonyms.remove($1)) ret format("Synonym * not found in " + c.synonyms); |
166 | change(); |
167 | if (l(c.synonyms) <= 1) synonyms.remove(c); |
168 | ret "OK"; |
169 | } |
170 | |
171 | if "list synonyms" |
172 | ret or2(lines(map(synonyms.clusters, c -> join(" = ", c.synonyms))), "No synonyms defined"); |
173 | |
174 | if (eqic(s, "help")) |
175 | ret ltrim([[ |
176 | I execute simple English rules. |
177 | |
178 | Stuff to say. |
179 | @me `on "hello bot" say "who are you"` |
180 | @me `on keyword "time" say "it is always time for a tea"` |
181 | @me `on "what is X" google X` |
182 | @me `on "show me X" image-google X` |
183 | @me `on bot keyword "game" and keyword "sure" say "let's play tic tac toe"` |
184 | @me `when @user comes online, say "hello friend"` |
185 | @me `rules` / `rules as file` - list rules |
186 | @me `delete rule abcdefghijkl` - delete rule with ID |
187 | |
188 | @me `add synonyms thx "thank you" thanks` - add synonyms |
189 | @me `delete synonym cluster thx` - delete all synonyms of thx |
190 | @me `delete synonym thx from cluster thanks` - delete a single synonym |
191 | @me `list synonyms` |
192 | |
193 | @me `priority channel on` - respond to every msg |
194 | @me `priority channel off` - respond to msgs with my name only |
195 | @me `masters` / `add master <username>` / `delete master <username>` |
196 | @me `help` / `support` / `source code` |
197 | @me `download` - download my brain for this guild |
198 | |
199 | To fill me with rules, just upload the file you got from `rules as file` to the channel. |
200 | |
201 | [Bot made by https://BotCompany.de] |
202 | ]]).replace("@me", atSelf()); |
203 | |
204 | // find rule to execute |
205 | |
206 | new LinkedHashSet<S> outs; |
207 | |
208 | for (Rule r : rules) pcall { |
209 | // IMPORTANT: patterns are duplicated above |
210 | |
211 | if (match("on * say *", r.text, m)) { |
212 | S in = $1, out = $2; |
213 | if (matchWithSynonyms(in, s)) |
214 | outs.add(rewrite(out, _)); |
215 | } |
216 | |
217 | if (match("on * or * say *", r.text, m)) { |
218 | S in1 = $1, in2 = $2, out = $3; |
219 | if (matchWithSynonyms(in1, s) || matchWithSynonyms(in2, s)) |
220 | outs.add(rewrite(out, _)); |
221 | } |
222 | |
223 | if (match("on keyword * and * say *", r.text, m)) { |
224 | S in1 = $1, in2 = $2, out = $3; |
225 | if (findWithSynonyms(in1, s) && findWithSynonyms(in2, s)) |
226 | outs.add(rewrite(out, _)); |
227 | } |
228 | |
229 | if (match("on keyword * or * say *", r.text, m)) { |
230 | S in1 = $1, in2 = $2, out = $3; |
231 | if (findWithSynonyms(in1, s) || findWithSynonyms(in2, s)) |
232 | outs.add(rewrite(out, _)); |
233 | } |
234 | |
235 | if (match("on bot keyword * and keyword *, say *", r.text, m)) { |
236 | ISaid lastSaid = lastSaidInChannel.get(channelID); |
237 | if (lastSaid != null) { |
238 | S in1 = $1, in2 = $2, out = $3; |
239 | if (findWithSynonyms(in1, lastSaid.text) && findWithSynonyms(in2, s)) |
240 | outs.add(rewrite(out, _)); |
241 | } |
242 | } |
243 | |
244 | if (match("on keyword * say *", r.text, m)) { |
245 | S in = $1, out = $2; |
246 | if (findWithSynonyms(in, s)) |
247 | outs.add(rewrite(out, _)); |
248 | } |
249 | |
250 | if (match("on *, image-google X", r.text, m)) |
251 | if (flexMatchIC(replaceWord($1, "X", "*"), s, m)) { |
252 | bool nsfw = discord_isNSFWChannel_gen(optPar channel(_)); |
253 | print(+nsfw); |
254 | BufferedImage img = nsfw ? quickVisualize_nsfw($1) : quickVisualize($1); |
255 | if (img == null) ret "No image found, sorry!"; |
256 | postImage(paramsToMap(_), uploadToImageServer(img, $1)); |
257 | } |
258 | |
259 | if (match("on *, google X", r.text, m)) |
260 | if (flexMatchIC(replaceWord($1, "X", "*"), s, m)) { |
261 | bool nsfw = discord_isNSFWChannel_gen(optPar channel(_)); |
262 | print(+nsfw); |
263 | ret discord_google($1, results := 1, safeSearch := !nsfw); |
264 | } |
265 | } |
266 | |
267 | if "download" { |
268 | optPar Guild guild; |
269 | optPar MessageChannel channel; |
270 | ret serveGuildBackup(channel, guild, |
271 | structure_nullingInstancesOfClass(_getClass(module()), ByServer.this)); |
272 | } |
273 | |
274 | if "stats" |
275 | ret "I have " + nRules(rules) + " and know " + n2(synonyms.totalCount(), "synonym"); |
276 | |
277 | try answer random(outs); |
278 | |
279 | if (enableMagicQuotes) |
280 | try answer answer_magicQuoteInput(s, useTranspiled := true); |
281 | null; |
282 | } |
283 | |
284 | S rewrite(S answer, O... _) { |
285 | if (containsIC(answer, "<user>")) { |
286 | optPar S name; |
287 | if (empty(name)) name = discord_optParUserName(_); |
288 | if (nempty(name)) |
289 | answer = replaceIC(answer, "<user>", name); |
290 | } |
291 | ret answer; |
292 | } |
293 | |
294 | void onOnlineStatusChange(UserUpdateOnlineStatusEvent e) { |
295 | User user = e.getMember().getUser(); |
296 | print("User status change: " + user + " " + e.getOldValue() + " -> " + e.getNewValue()); |
297 | |
298 | // only react to user coming online |
299 | if (e.getOldValue() != OnlineStatus.OFFLINE) ret; |
300 | |
301 | long id = user.getIdLong(); |
302 | |
303 | new LinkedHashSet<S> outs; |
304 | new Matches m; |
305 | |
306 | for (Rule r : rules) pcall { |
307 | // IMPORTANT: patterns are duplicated above |
308 | |
309 | if (match("when <*> comes online, say *", r.text, m)) { |
310 | long _id = parseLongOpt($1); |
311 | S out = $2; |
312 | if (id == _id) |
313 | outs.add(rewrite(out, name := user.getName())); |
314 | } |
315 | } |
316 | |
317 | postInChannel(preferredChannelID, random(outs)); |
318 | } |
319 | |
320 | bool findWithSynonyms(S pat, S s) { |
321 | for (S pat2 : synonyms.extend(pat)) |
322 | if (find3(pat2, s)) |
323 | true; |
324 | false; |
325 | } |
326 | |
327 | bool matchWithSynonyms(S pat, S s) { |
328 | for (S pat2 : synonyms.extend(pat)) |
329 | if (match(pat2, s)) |
330 | true; |
331 | false; |
332 | } |
333 | } |
334 | |
335 | long totalRuleCount() { |
336 | ret intSumAsLong(map(syncCloneValues(dataByServer), bs -> l(bs.rules))); |
337 | } |
338 | } |
Began life as a copy of #1025304
download show line numbers debug dex old transpilations
Travelled to 8 computer(s): bhatertpkbcr, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, whxojlpjdney
No comments. add comment
Snippet ID: | #1025348 |
Snippet name: | NL Rules Bot [LIVE in Gazelle, copy of Katherine] |
Eternal ID of this version: | #1025348/95 |
Text MD5: | 30c5362559a2ac9eb9e8ce229a0cba63 |
Transpilation MD5: | e2c67f92b82ac91e415da06c936e1b7a |
Author: | stefan |
Category: | javax |
Type: | JavaX source code (Dynamic Module) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-10-23 12:31:29 |
Source code size: | 11655 bytes / 338 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 491 / 25548 |
Version history: | 94 change(s) |
Referenced in: | [show references] |