Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

282
LINES

< > BotCompany Repo | #1034054 // DynShardedTalkBot2 renamed to DynTalkBot2 for legacy data

JavaX fragment (include)

set flag JDA40.

import net.dv8tion.jda.api.events.Event;
import net.dv8tion.jda.api.events.user.update.*;

asclass DynTalkBot2<A extends DynTalkBot2.ByServer> extends DynServerAwareDiscordBot<A> {
  switchable S myName = "Anonymous bot";
  transient bool useAGIBlueForDropPunctuation = true;
  transient bool preprocessAtSelfToMyName = true;
  transient bool dropPunctuation = true;
  transient bool ngbCommands = true;
  L<Long> 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<DynTalkBot2.ByServer>.ByServer {
    transient Lock lock;
    L<Long> guildAuthorizedUsers = synchroList();
    Map<Long, ISaid> 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));
  }
}

Author comment

Began life as a copy of #1034052

download  show line numbers  debug dex  old transpilations   

Travelled to 3 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj

No comments. add comment

Snippet ID: #1034054
Snippet name: DynShardedTalkBot2 renamed to DynTalkBot2 for legacy data
Eternal ID of this version: #1034054/1
Text MD5: daf3d2df7e1dd5f19c8f1dc359616060
Author: stefan
Category: javax / discord
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-01-19 17:07:44
Source code size: 9031 bytes / 282 lines
Pitched / IR pitched: No / No
Views / Downloads: 99 / 133
Referenced in: #1034058 - MKLab FM Bot with JDA 4.2 [using renamed sharded classes]