set flag JDA40. import net.dv8tion.jda.api.events.Event; import net.dv8tion.jda.api.events.user.update.*; 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 GuildChannel) { 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) {} // to change name by server S myName() { ret myName; } @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: " + quote(s)); //pcall { print("mother: " + DynTalkBot2.this); } if (preprocessAtSelfToMyName && discordBotID != 0) { //S a = dropPunctuation ? dropPunctuation3(atSelf()) : atSelf(); LS l = ll(atSelf(), atExclamSelf()); print("Replacing " + sfu(l)); s = replace_multi(s, l, " " + myName + " "); print("Got " + quote(s)); } 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 linesLL("General: " + renderMasters(), "For this guild: " + renderGuildMasters()); if (eqic(input, "guild masters")) ret renderGuildMasters(); 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 (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... _) { if (authed(_)) null; 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); } // e.g. for help texts S atSelfOrMyName() { ret preprocessAtSelfToMyName ? myName() : atSelf(); } S atSelfOrMyName_space() { ret preprocessAtSelfToMyName ? appendSpaceIfEndsWithLetter(myName) : atSelf(); } S guildMastersHelp() { ret [[ @me **masters** -- show who controls me @me **add guild master @user** -- allow @user to control me @me **delete guild master @user** -- @user can no longer control me ]]; } } // 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(discordBotName) + "-" + 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()); } S renderMasters() { ret empty(authorizedUsers) ? "I have no masters." : "My masters are: " + joinWithComma(map discordAtPlusID(authorizedUsers)); } }