abstract sclass DynNLRulesBot extends DynTalkBot2 {
void init {
super.init();
useAGIBlueForDropPunctuation = false;
preprocessAtSelfToMyName = false;
dropPunctuation = false;
print("Total rules in all guilds: " + totalRuleCount());
}
sclass Rule {
GlobalID globalID = aGlobalIDObj();
S text;
*() {} *(S *text) {}
}
S renderRule(Rule r) {
ret r == null ? null : "Rule " + r.globalID + ": " + r.text;
}
class ByServer extends DynTalkBot2.ByServer {
new L rules;
new Set priorityChannels; // where we always respond
bool enableMagicQuotes = true;
synchronized S processSimplifiedLine(S s, O... _) {
try answer super.processSimplifiedLine(s, _);
print("s=" + s);
new Matches m;
S s2 = dropMyPrefixOrNull(s);
if (s2 != null) s = s2;
else if (!contains(priorityChannels, optPar channelID(_))) null;
optPar Message msg;
print("msg=" + msg);
Message.Attachment attachment = msg == null ? null : first(msg.getAttachments());
if (attachment != null) {
long guildID = idForByServer(this);
File file = prepareCacheProgramFile(guildID + "/" + attachment.getFileName());
print("Downloading attachment: " + file);
deleteFile(file);
if (!attachment.download(file)) ret "Couldn't download attachment";
LS lines = tlft(loadTextFile(file));
print("First line: " + first(lines));
if (!swic(first(lines), "rule ")) print("Ignoring attachment");
else {
new LPairS toImport; // (globalID, text)
for (S line : lines) {
LS l = regexpICFullMatch_groups("Rule ([a-z]+):(.*)$", line);
if (nempty(l))
toImport.add(listToPair(l));
}
S found = "Found " + nRules(toImport) + " in attachment";
int oldCount = l(rules);
Map existing = indexByField globalID(rules);
int updated = 0, added = 0;
for (PairS rule : toImport) {
GlobalID id = GlobalID(rule.a);
S text = trim(rule.b);
Rule r = existing.get(id);
if (r == null) {
r = new Rule(text);
r.globalID = id;
rules.add(r);
existing.put(id, r);
++added;
} else if (neq(r.text, text)) {
r.text = text;
++updated;
}
}
if (added+updated > 0) change();
ret found + ". Had: " + oldCount + ". Added: " + added + ". Updated: " + updated + ". New count: " + l(rules);
}
}
// duplicated patterns go here
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 *" {
Rule r = firstWhereIC(rules, text := s);
if (r != null) ret "Rule exists: " + r.globalID;
rules.add(r = new Rule(s));
change();
ret "Rule added with ID " + r.globalID;
}
if "rules"
ret or2(lines(map(r -> renderRule(r), rules)), "No rules defined yet");
if "rules as file" {
uploadFileInChannel(longPar channelID(_),
toUtf8(lines(map(r -> renderRule(r), rules))),
genericTextFileName(), null, null);
null;
}
if "delete rule *|delete rule with id *" {
try answer checkAuth(_);
Rule r = firstWhere(rules, globalID := GlobalID($1));
if (r == null) ret "Rule " + $1 + " not found";
rules.remove(r);
change();
ret "Rule deleted, " + nRule(rules) + " remain";
}
if "delete all rules" {
appendToTextFile(programFile("backups.txt"), guildStructure());
try answer checkAuth(_);
rules.clear();
change();
ret "All rules deleted";
}
if "priority channel on" {
try answer checkAuth(_);
priorityChannels.add(longOptPar channelID(_)); change();
ret "OK";
}
if "priority channel off" {
try answer checkAuth(_);
priorityChannels.remove(optPar channelID(_)); change();
ret "OK";
}
if "disable magic quotes" {
try answer checkAuth(_);
enableMagicQuotes = false; change();
ret "OK";
}
if (eqic(s, "help"))
ret ltrim([[
I execute simple English rules.
Stuff to say.
@me `on "hello bot" say "who are you"`
@me `on keyword "time" say "it is always time for a tea"`
@me `on "what is X" google X`
@me `on "show me X" image-google X`
@me `when @user comes online, say "hello friend"`
@me `rules` / `rules as file` - list rules
@me `delete rule abcdefghijkl` - delete rule with ID
@me `priority channel on` - respond to every msg
@me `priority channel off` - respond to msgs with my name only
@me `masters` / `add master ` / `delete master `
@me `help` / `support` / `source code`
@me `download` - download my brain for this guild
To fill me with rules, just upload the file you got from `rules as file` to the channel.
[Bot made by https://BotCompany.de]
]]).replace("@me", atSelf());
// find rule to execute
new LinkedHashSet outs;
for (Rule r : rules) pcall {
// IMPORTANT: patterns are duplicated above
if (match("on * say *", r.text, m)) {
S in = $1, out = $2;
if (match(in, s))
outs.add(rewrite(out, _));
}
if (match("on * or * say *", r.text, m)) {
S in1 = $1, in2 = $2, out = $3;
if (match(in1, s) || match(in2, s))
outs.add(rewrite(out, _));
}
if (match("on keyword * and * say *", r.text, m)) {
S in1 = $1, in2 = $2, out = $3;
if (find3(in1, s) && find3(in2, s))
outs.add(rewrite(out, _));
}
if (match("on keyword * or * say *", r.text, m)) {
S in1 = $1, in2 = $2, out = $3;
if (find3(in1, s) || find3(in2, s))
outs.add(rewrite(out, _));
}
if (match("on keyword * say *", r.text, m)) {
S in = $1, out = $2;
if (find3(in, s))
outs.add(rewrite(out, _));
}
if (match("on *, image-google X", r.text, m))
if (flexMatchIC(replaceWord($1, "X", "*"), s, m)) {
bool nsfw = discord_isNSFWChannel_gen(optPar channel(_));
print(+nsfw);
BufferedImage img = nsfw ? quickVisualize_nsfw($1) : quickVisualize($1);
if (img == null) ret "No image found, sorry!";
postImage(paramsToMap(_), uploadToImageServer(img, $1));
}
if (match("on *, google X", r.text, m))
if (flexMatchIC(replaceWord($1, "X", "*"), s, m)) {
bool nsfw = discord_isNSFWChannel_gen(optPar channel(_));
print(+nsfw);
ret discord_google($1, results := 1, safeSearch := !nsfw);
}
}
if "download" {
optPar Guild guild;
optPar MessageChannel channel;
ret serveGuildBackup(channel, guild,
structure_nullingInstancesOfClass(_getClass(module()), ByServer.this));
}
if "stats"
ret "I have " + nRules(rules);
try answer random(outs);
if (enableMagicQuotes)
try answer answer_magicQuoteInput(s, useTranspiled := true);
null;
}
S rewrite(S answer, O... _) {
if (containsIC(answer, "")) {
optPar S name;
if (empty(name)) name = discord_optParUserName(_);
if (nempty(name))
answer = replaceIC(answer, "", name);
}
ret answer;
}
void onOnlineStatusChange(UserUpdateOnlineStatusEvent e) {
User user = e.getMember().getUser();
print("User status change: " + user + " " + e.getOldValue() + " -> " + e.getNewValue());
// only react to user coming online
if (e.getOldValue() != OnlineStatus.OFFLINE) ret;
long id = user.getIdLong();
new LinkedHashSet outs;
new Matches m;
for (Rule r : rules) pcall {
// IMPORTANT: patterns are duplicated above
if (match("when <*> comes online, say *", r.text, m)) {
long _id = parseLongOpt($1);
S out = $2;
if (id == _id)
outs.add(rewrite(out, name := user.getName()));
}
}
postInChannel(preferredChannelID, random(outs));
}
}
long totalRuleCount() {
ret intSumAsLong(map(syncCloneValues(dataByServer), bs -> l(bs.rules)));
}
}