ifdef JDA40
import net.dv8tion.jda.api.events.Event;
import net.dv8tion.jda.api.events.user.update.*;
endifdef
ifndef JDA40
import net.dv8tion.jda.core.events.Event;
import net.dv8tion.jda.core.events.user.update.*;
endifndef
asclass DynTalkBot2 extends DynServerAwareDiscordBot {
switchable S myName = "Anonymous bot";
transient bool useAGIBlueForDropPunctuation = true;
transient bool preprocessAtSelfToMyName = true;
transient bool dropPunctuation = true;
transient bool ngbCommands = true;
L authorizedUsers = ll(547706854680297473); // stefan
srecord ISaid(long channelID, long date, S text) {}
void init {
super.init();
onPostedInChannel.add((channel, msg) -> {
if (channel cast Channel) {
Guild guild = channel.getGuild();
if (guild == null) ret;
ByServer bs = getByServer(guild);
long channelID = channel.getIdLong();
mapPut(bs.lastSaidInChannel, channelID, nu ISaid(+channelID, text := msg));
change();
}
});
// We leave it as null to force subclasses to set this
// (avoids serializing bad instances)
// makeByServer = () -> (A) new ByServer;
dm_vmBus_onMessage_q discordGuildJoin(voidfunc(Map map) {
ret unless map.get('module) == module();
print("Got join");
getByServer(getLong guildID(map), true)
.onUserJoin(getLong userID(map), map);
});
onDiscordEvent(e -> {
//print("Generic Discord event: " + e);
if (e cast UserUpdateOnlineStatusEvent)
getByServer(e.getGuild()).onOnlineStatusChange(e);
});
}
class ByServer extends DynServerAwareDiscordBot.ByServer {
transient Lock lock;
L guildAuthorizedUsers = synchroList();
Map lastSaidInChannel = synchroMap();
long preferredChannelInGuild;
DynTalkBot2 m2() { ret DynTalkBot2.this; } // for debugging
// overridable
void onUserJoin(long userID, O... _) {}
void onOnlineStatusChange(UserUpdateOnlineStatusEvent e) {}
@Override S answer(S input, Map map) {
if (preferredChannelInGuild == 0) {
long channelID = longPar channelID(map);
if (channelID != 0) {
preferredChannelInGuild = channelID;
change();
}
}
S answer = processLine(input, map);
try answer super.answer(input, map); // server-aware stuff
ret answer;
}
S dropMyPrefixOpt(S s) {
ret or(dropMyPrefixOrNull(s), s);
}
S dropMyPrefixOrNull(S s) {
S sOld = s;
s = dropPrefixICTrim_orNull(myPrefix(), s);
if (s == null)
print("no got prefix: " + quote(myPrefix()) + " / " + quote(sOld));
ret s;
}
S processLine(S s, O... _) {
s = preprocess(s, _);
ret processSimplifiedLine(s, _);
}
S preprocess(S s, O... _) {
print("Preprocessing: " + s);
//pcall { print("mother: " + DynTalkBot2.this); }
if (preprocessAtSelfToMyName && discordBotID != 0)
s = replace(s, atSelf(), " " + myName + " ");
if (dropPunctuation)
s = dropPunctuation3_withAGIBlue(useAGIBlueForDropPunctuation, s);
s = trim(simpleSpaces_noTok(s));
print("simplified >> " + quote(s));
ret s;
}
S myPrefix() {
ret preprocessAtSelfToMyName ?
(endsWithLetterOrDigit(myName) ? myName + " " : myName) :
(dropPunctuation ? replace(atSelf(), "@", "") /* @ is killed by preprocessing */ : atSelf()) + " ";
}
synchronized Lock myLock() {
if (lock == null) lock = lock();
ret lock;
}
// extend me
S processSimplifiedLine(S input, O... _) {
lock myLock();
new Matches m;
input = dropMyPrefixOrNull(input);
if (input == null) null;
if (ngbCommands) {
if (eqicOneOf(input, "support channel", "support server", "support"))
ret "Get support for me here: " + nextGenBotsDiscordInvite();
if (eqicOneOf(input, "source", "sources", "source code"))
ret snippetLink(programID());
}
if (swic_trim(input, "add master ", m)) {
try answer checkAuth(_);
setAdd(authorizedUsers, parseFirstLong(m.rest()));
change();
ret "Okidoki. Have " + n2(l(authorizedUsers), "master");
}
if (eqic(input, "masters"))
ret renderMasters();
if (swic_trim(input, "delete master ", m)) {
try answer checkAuth(_);
remove(authorizedUsers, parseFirstLong(m.rest()));
change();
ret "Okidoki. Have " + n2(l(authorizedUsers), "master");
}
if (swic_trim(input, "add guild master ", m)) {
try answer checkPerGuildAuth(_);
setAdd(guildAuthorizedUsers, parseFirstLong(m.rest()));
change();
ret "Okidoki. " + renderGuildMasters();
}
if (eqic(input, "guild masters"))
ret renderGuildMasters();
if (swic_trim(input, "delete guild master ", m)) {
try answer checkPerGuildAuth(_);
remove(guildAuthorizedUsers, parseFirstLong(m.rest()));
change();
ret "Okidoki. " + renderGuildMasters();
}
if (eqic(input, "guild count")) {
try answer checkAuth(_);
ret renderGuildCount();
}
if (eqic(input, "guild names")) {
try answer checkAuth(_);
ret renderGuildNames();
}
null;
}
S renderGuildMasters() {
ret empty(guildAuthorizedUsers) ? "Only this guild's owner can control me."
: "I am controlled by this guild's owner and these people: " + joinWithComma(map discordAtPlusID(guildAuthorizedUsers));
}
S renderGuildCount() {
ret "Total guilds joined: " + guildCount + ". Live guilds: " + liveGuildCount();
}
S renderGuildNames() {
ret lines(map(discord.getGuilds(), g -> g.getName()));
}
bool authed(O... _) {
ret contains(authorizedUsers, optPar userID(_));
}
bool perGuildAuthed(O... _) {
long userID = longPar userID(_);
ret contains(guildAuthorizedUsers, userID)
|| guildID != 0 && userID == getGuild().getOwnerIdLong();
}
Guild getGuild() {
ret guildID == 0 ? null : discord.getGuildById(guildID);
}
S checkAuth(O... _) {
long userID = longPar userID(_);
bool result = authed(_);
print("Auth-checking user ID: " + userID + " => " + result);
if (!result) ret "You are not authorized";
null;
}
S checkPerGuildAuth(O... _) {
long userID = longPar userID(_);
bool result = perGuildAuthed(_);
print("Guild-auth-checking user ID: " + userID + " => " + result);
if (!result) ret "You are not authorized";
null;
}
S guildStructure() {
ret structure_nullingInstancesOfClass(_getClass(module()), this);
}
} // end of class ByServer
S serveGuildBackup(MessageChannel channel, Guild guild, S data) {
if (guild == null) ret "Do this in a guild";
if (containsDiscordToken(data)) ret "DISCORD TOKEN EXPOSED ALARM!! NOTIFY ADMINISTRATOR";
S baseName = urlencode(jda_selfUserName(discord))
+ "-" + guild.getIdLong() + "-" + ymd_minus_hms();
S zipName = baseName + ".zip";
File zip = programFile("backups/" + zipName);
createZipFileWithSingleTextFile(zip, baseName + ".txt", data);
channel.sendMessage("Here's my latest brain contents for this guild.").addFile(zip).queue();
null;
}
// use structure() on ByServer
S serveGuildBackup(MessageChannel channel, Guild guild, ByServer bs) {
ret serveGuildBackup(channel, guild, bs.guildStructure());
}
// e.g. for help texts
S atSelfOrMyName() {
ret preprocessAtSelfToMyName ? myName : atSelf();
}
S atSelfOrMyName_space() {
ret preprocessAtSelfToMyName ? appendSpaceIfEndsWithLetter(myName) : atSelf();
}
S renderMasters() {
ret empty(authorizedUsers) ? "I have no masters." : "My masters are: " + joinWithComma(map discordAtPlusID(authorizedUsers));
}
}