| 1 | set flag JDA40. | 
| 2 | |
| 3 | import net.dv8tion.jda.api.events.Event; | 
| 4 | import net.dv8tion.jda.api.events.user.update.*; | 
| 5 | |
| 6 | asclass DynTalkBot2<A extends DynTalkBot2.ByServer> extends DynServerAwareDiscordBot<A> {
 | 
| 7 | switchable S myName = "Anonymous bot"; | 
| 8 | transient bool useAGIBlueForDropPunctuation = true; | 
| 9 | transient bool preprocessAtSelfToMyName = true; | 
| 10 | transient bool dropPunctuation = true; | 
| 11 | transient bool ngbCommands = true; | 
| 12 | L<Long> authorizedUsers = ll(547706854680297473); // stefan | 
| 13 | |
| 14 |   srecord ISaid(long channelID, long date, S text) {}
 | 
| 15 | |
| 16 |   void init {
 | 
| 17 | super.init(); | 
| 18 | |
| 19 |     onPostedInChannel.add((channel, msg) -> {
 | 
| 20 |       if (channel cast GuildChannel) {
 | 
| 21 | Guild guild = channel.getGuild(); | 
| 22 | if (guild == null) ret; | 
| 23 | ByServer bs = getByServer(guild); | 
| 24 | long channelID = channel.getIdLong(); | 
| 25 | mapPut(bs.lastSaidInChannel, channelID, nu ISaid(+channelID, text := msg)); | 
| 26 | change(); | 
| 27 | } | 
| 28 | }); | 
| 29 | |
| 30 | // We leave it as null to force subclasses to set this | 
| 31 | // (avoids serializing bad instances) | 
| 32 | // makeByServer = () -> (A) new ByServer; | 
| 33 | |
| 34 |     dm_vmBus_onMessage_q discordGuildJoin(voidfunc(Map map) {
 | 
| 35 |       ret unless map.get('module) == module();
 | 
| 36 |       print("Got join");
 | 
| 37 | getByServer(getLong guildID(map), true) | 
| 38 | .onUserJoin(getLong userID(map), map); | 
| 39 | }); | 
| 40 | |
| 41 |     onDiscordEvent(e -> {
 | 
| 42 |       //print("Generic Discord event: " + e);
 | 
| 43 | if (e cast UserUpdateOnlineStatusEvent) | 
| 44 | getByServer(e.getGuild()).onOnlineStatusChange(e); | 
| 45 | }); | 
| 46 | } | 
| 47 | |
| 48 |   class ByServer extends DynServerAwareDiscordBot<DynTalkBot2.ByServer>.ByServer {
 | 
| 49 | transient Lock lock; | 
| 50 | L<Long> guildAuthorizedUsers = synchroList(); | 
| 51 | Map<Long, ISaid> lastSaidInChannel = synchroMap(); | 
| 52 | long preferredChannelInGuild; | 
| 53 | |
| 54 |     DynTalkBot2 m2() { ret DynTalkBot2.this; } // for debugging
 | 
| 55 | |
| 56 | // overridable | 
| 57 |     void onUserJoin(long userID, O... _) {}
 | 
| 58 |     void onOnlineStatusChange(UserUpdateOnlineStatusEvent e) {}
 | 
| 59 | |
| 60 | // to change name by server | 
| 61 |     S myName() { ret myName; }
 | 
| 62 | |
| 63 |     @Override S answer(S input, Map map) {
 | 
| 64 |       if (preferredChannelInGuild == 0) {
 | 
| 65 | long channelID = longPar channelID(map); | 
| 66 |         if (channelID != 0) {
 | 
| 67 | preferredChannelInGuild = channelID; | 
| 68 | change(); | 
| 69 | } | 
| 70 | } | 
| 71 | |
| 72 | S answer = processLine(input, map); | 
| 73 | try answer super.answer(input, map); // server-aware stuff | 
| 74 | |
| 75 | ret answer; | 
| 76 | } | 
| 77 | |
| 78 |     S dropMyPrefixOpt(S s) {
 | 
| 79 | ret or(dropMyPrefixOrNull(s), s); | 
| 80 | } | 
| 81 | |
| 82 |     S dropMyPrefixOrNull(S s) {
 | 
| 83 | S sOld = s; | 
| 84 | s = dropPrefixICTrim_orNull(myPrefix(), s); | 
| 85 | if (s == null) | 
| 86 |         print("no got prefix: " + quote(myPrefix()) + " / " + quote(sOld));
 | 
| 87 | ret s; | 
| 88 | } | 
| 89 | |
| 90 |     S processLine(S s, O... _) {
 | 
| 91 | s = preprocess(s, _); | 
| 92 | ret processSimplifiedLine(s, _); | 
| 93 | } | 
| 94 | |
| 95 |     S preprocess(S s, O... _) {
 | 
| 96 |       print("Preprocessing: " + quote(s));
 | 
| 97 |       //pcall { print("mother: " + DynTalkBot2.this); }
 | 
| 98 |       if (preprocessAtSelfToMyName && discordBotID != 0) {
 | 
| 99 | //S a = dropPunctuation ? dropPunctuation3(atSelf()) : atSelf(); | 
| 100 | LS l = ll(atSelf(), atExclamSelf()); | 
| 101 |         print("Replacing " + sfu(l));
 | 
| 102 | s = replace_multi(s, l, " " + myName + " "); | 
| 103 |         print("Got " + quote(s));
 | 
| 104 | } | 
| 105 | if (dropPunctuation) | 
| 106 | s = dropPunctuation3_withAGIBlue(useAGIBlueForDropPunctuation, s); | 
| 107 | s = trim(simpleSpaces_noTok(s)); | 
| 108 |       print("simplified >> " + quote(s));
 | 
| 109 | ret s; | 
| 110 | } | 
| 111 | |
| 112 |     S myPrefix() {
 | 
| 113 | ret preprocessAtSelfToMyName ? | 
| 114 | (endsWithLetterOrDigit(myName()) ? myName() + " " : myName()) : | 
| 115 | (dropPunctuation ? replace(atSelf(), "@", "") /* @ is killed by preprocessing */ : atSelf()) + " "; | 
| 116 | } | 
| 117 | |
| 118 |     synchronized Lock myLock() {
 | 
| 119 | if (lock == null) lock = lock(); | 
| 120 | ret lock; | 
| 121 | } | 
| 122 | |
| 123 | // extend me | 
| 124 |     S processSimplifiedLine(S input, O... _) {
 | 
| 125 | lock myLock(); | 
| 126 | new Matches m; | 
| 127 | |
| 128 | input = dropMyPrefixOrNull(input); | 
| 129 | if (input == null) null; | 
| 130 | |
| 131 |       if (ngbCommands) {
 | 
| 132 | if (eqicOneOf(input, "support channel", "support server", "support")) | 
| 133 | ret "Get support for me here: " + nextGenBotsDiscordInvite(); | 
| 134 | |
| 135 | if (eqicOneOf(input, "source", "sources", "source code")) | 
| 136 | ret snippetLink(programID()); | 
| 137 | } | 
| 138 | |
| 139 |       if (swic_trim(input, "add master ", m)) {
 | 
| 140 | try answer checkAuth(_); | 
| 141 | |
| 142 | setAdd(authorizedUsers, parseFirstLong(m.rest())); | 
| 143 | change(); | 
| 144 | |
| 145 | ret "Okidoki. Have " + n2(l(authorizedUsers), "master"); | 
| 146 | } | 
| 147 | |
| 148 | if (eqic(input, "masters")) | 
| 149 |         ret linesLL("General: " + renderMasters(),
 | 
| 150 | "For this guild: " + renderGuildMasters()); | 
| 151 | |
| 152 | if (eqic(input, "guild masters")) | 
| 153 | ret renderGuildMasters(); | 
| 154 | |
| 155 |       if (swic_trim(input, "delete master ", m)) {
 | 
| 156 | try answer checkAuth(_); | 
| 157 | |
| 158 | remove(authorizedUsers, parseFirstLong(m.rest())); | 
| 159 | change(); | 
| 160 | |
| 161 | ret "Okidoki. Have " + n2(l(authorizedUsers), "master"); | 
| 162 | } | 
| 163 | |
| 164 |       if (swic_trim(input, "add guild master ", m)) {
 | 
| 165 | try answer checkPerGuildAuth(_); | 
| 166 | |
| 167 | setAdd(guildAuthorizedUsers, parseFirstLong(m.rest())); | 
| 168 | change(); | 
| 169 | |
| 170 | ret "Okidoki. " + renderGuildMasters(); | 
| 171 | } | 
| 172 | |
| 173 |       if (swic_trim(input, "delete guild master ", m)) {
 | 
| 174 | try answer checkPerGuildAuth(_); | 
| 175 | |
| 176 | remove(guildAuthorizedUsers, parseFirstLong(m.rest())); | 
| 177 | change(); | 
| 178 | |
| 179 | ret "Okidoki. " + renderGuildMasters(); | 
| 180 | } | 
| 181 | |
| 182 |       if (eqic(input, "guild count")) {
 | 
| 183 | try answer checkAuth(_); | 
| 184 | ret renderGuildCount(); | 
| 185 | } | 
| 186 | |
| 187 |       if (eqic(input, "guild names")) {
 | 
| 188 | try answer checkAuth(_); | 
| 189 | ret renderGuildNames(); | 
| 190 | } | 
| 191 | |
| 192 | null; | 
| 193 | } | 
| 194 | |
| 195 |     S renderGuildMasters() {
 | 
| 196 | ret empty(guildAuthorizedUsers) ? "Only this guild's owner can control me." | 
| 197 | : "I am controlled by this guild's owner and these people: " + joinWithComma(map discordAtPlusID(guildAuthorizedUsers)); | 
| 198 | } | 
| 199 | |
| 200 |     S renderGuildCount() {
 | 
| 201 | ret "Total guilds joined: " + guildCount + ". Live guilds: " + liveGuildCount(); | 
| 202 | } | 
| 203 | |
| 204 |     S renderGuildNames() {
 | 
| 205 | ret lines(map(discord.getGuilds(), g -> g.getName())); | 
| 206 | } | 
| 207 | |
| 208 |     bool authed(O... _) {
 | 
| 209 | ret contains(authorizedUsers, optPar userID(_)); | 
| 210 | } | 
| 211 | |
| 212 |     bool perGuildAuthed(O... _) {
 | 
| 213 | long userID = longPar userID(_); | 
| 214 | ret contains(guildAuthorizedUsers, userID) | 
| 215 | || guildID != 0 && userID == getGuild().getOwnerIdLong(); | 
| 216 | } | 
| 217 | |
| 218 |     Guild getGuild() {
 | 
| 219 | ret guildID == 0 ? null : discord.getGuildById(guildID); | 
| 220 | } | 
| 221 | |
| 222 |     S checkAuth(O... _) {  
 | 
| 223 | long userID = longPar userID(_); | 
| 224 | bool result = authed(_); | 
| 225 |       print("Auth-checking user ID: " + userID + " => " + result);
 | 
| 226 | if (!result) ret "You are not authorized"; | 
| 227 | null; | 
| 228 | } | 
| 229 | |
| 230 |     S checkPerGuildAuth(O... _) {  
 | 
| 231 | if (authed(_)) null; | 
| 232 | long userID = longPar userID(_); | 
| 233 | bool result = perGuildAuthed(_); | 
| 234 |       print("Guild-auth-checking user ID: " + userID + " => " + result);
 | 
| 235 | if (!result) ret "You are not authorized"; | 
| 236 | null; | 
| 237 | } | 
| 238 | |
| 239 |     S guildStructure() {
 | 
| 240 | ret structure_nullingInstancesOfClass(_getClass(module()), this); | 
| 241 | } | 
| 242 | |
| 243 | // e.g. for help texts | 
| 244 |     S atSelfOrMyName() {
 | 
| 245 | ret preprocessAtSelfToMyName ? myName() : atSelf(); | 
| 246 | } | 
| 247 | |
| 248 |     S atSelfOrMyName_space() {
 | 
| 249 | ret preprocessAtSelfToMyName ? appendSpaceIfEndsWithLetter(myName) : atSelf(); | 
| 250 | } | 
| 251 | |
| 252 |     S guildMastersHelp() {
 | 
| 253 | ret [[ | 
| 254 | @me **masters** -- show who controls me | 
| 255 | @me **add guild master @user** -- allow @user to control me | 
| 256 | @me **delete guild master @user** -- @user can no longer control me | 
| 257 | ]]; | 
| 258 | } | 
| 259 | |
| 260 | } // end of class ByServer | 
| 261 | |
| 262 |   S serveGuildBackup(MessageChannel channel, Guild guild, S data) {
 | 
| 263 | if (guild == null) ret "Do this in a guild"; | 
| 264 | if (containsDiscordToken(data)) ret "DISCORD TOKEN EXPOSED ALARM!! NOTIFY ADMINISTRATOR"; | 
| 265 | S baseName = urlencode(jda_selfUserName(discord)) | 
| 266 | + "-" + guild.getIdLong() + "-" + ymd_minus_hms(); | 
| 267 | S zipName = baseName + ".zip"; | 
| 268 |     File zip = programFile("backups/" + zipName);
 | 
| 269 | createZipFileWithSingleTextFile(zip, baseName + ".txt", data); | 
| 270 |     channel.sendMessage("Here's my latest brain contents for this guild.").addFile(zip).queue();
 | 
| 271 | null; | 
| 272 | } | 
| 273 | |
| 274 | // use structure() on ByServer | 
| 275 |   S serveGuildBackup(MessageChannel channel, Guild guild, ByServer bs) {
 | 
| 276 | ret serveGuildBackup(channel, guild, bs.guildStructure()); | 
| 277 | } | 
| 278 | |
| 279 |   S renderMasters() {
 | 
| 280 | ret empty(authorizedUsers) ? "I have no masters." : "My masters are: " + joinWithComma(map discordAtPlusID(authorizedUsers)); | 
| 281 | } | 
| 282 | } | 
Began life as a copy of #1025128
download show line numbers debug dex old transpilations
Travelled to 9 computer(s): bhatertpkbcr, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, qsqiayxyrbia, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
| Snippet ID: | #1026298 | 
| Snippet name: | [LIVE] DynTalkBot2 for JDA 4.0 - DynServerAwareDiscordBot + input simplification, onUserJoin, name, authorizedUsers | 
| Eternal ID of this version: | #1026298/17 | 
| Text MD5: | 1bc86b4c9b5693a46a57600fd9790f52 | 
| Author: | stefan | 
| Category: | javax / discord | 
| Type: | JavaX fragment (include) | 
| Public (visible to everyone): | Yes | 
| Archived (hidden from active list): | No | 
| Created/modified: | 2021-01-24 02:25:29 | 
| Source code size: | 9042 bytes / 282 lines | 
| Pitched / IR pitched: | No / No | 
| Views / Downloads: | 668 / 1478 | 
| Version history: | 16 change(s) | 
| Referenced in: | [show references] |