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

393
LINES

< > BotCompany Repo | #1022073 // Discord Bot [Gazelle-based, v11, with new cache, old]

JavaX source code (Dynamic Module) [tags: use-pretranspiled] - run with: Stefan's OS

Uses 9211K of libraries. Click here for Pure Java version (21230L/129K).

!7

set flag DynModule.

cmodule DiscordBot > DynPrintLogAndEnabled {
  transient JDA bot;
  transient new Set<Long> msgsReactedTo;
  transient double deleteDelay = 60.0;
  transient bool doDelete;
  transient int maxMonologue = 5;
  transient Color imageEmbedMysteriousLineColor = colorFromHex("36393f");

  transient new InheritableThreadLocal<MessageChannel> currentChannel;
  transient new InheritableThreadLocal<Message> respondingTo;
  
  transient new L<Long> lastPosted;
  transient GazelleContextCache_v2 contextCache;
  
  transient Set<S> acceptablePurposes = litciset("");

  start {
    logModuleOutput();
    dm_useLocalMechListCopies();
    
    print("Making cache");
    time "Made cache" {
      contextCache = new GazelleContextCache_v2(this);
      ownResource(contextCache.listenToMessages());
      contextCache.fill();
    }
    
    // TODO: log JDA output
    bot = discordBot(new ListenerAdapter {
      public void onMessageUpdate(MessageUpdateEvent e) pcall {
        temp enter();
        ret if !enabled || !licensed();
        
        long msgID = e.getMessage().getIdLong();
        S content = e.getMessage().getContentRaw();
        O lineConcept = dm_discord_lineForMsgID_unimported(msgID);
        S rendered = msgID + ": " + vm_cleanForPrint(content);
        if (lineConcept == null)
          ret with print("Weird: Message edited, but not found: " + rendered);
        call(lineConcept, '_setField, editedText := content);
        print("Message edited: " + rendered);
      }
      
      public void onMessageReceived(MessageReceivedEvent e) pcall {
        temp enter();
        ret if !enabled || !licensed();
        
        bool bot = e.getAuthor().isBot();
        long msgID = e.getMessage().getIdLong();
        long userID = e.getAuthor().getIdLong();
        Member member = e.getMember();
        S userName = member == null ? null : member.getNickname(); // the changeable nick name - null sometimes?
        if (userName == null && member != null) userName = member.getEffectiveName();
        if (userName == null) userName = e.getAuthor().getName();
        
        final Message msg = e.getMessage();
        
        print("Channel type: " + e.getChannelType());
        bool isPrivate = e.getChannelType() == ChannelType.PRIVATE;
        S content = trim(msg.getContentRaw());
        print("Msg from " + userName + ": " + vm_cleanForPrint(content));
        if (empty(content)) ret;
        fS originalContent = content;

        O user = userConcept(e.getAuthor());
        
        S crud = dm_gazelle_linesCRUD();
        O channel = dm_call(crud, 'uniqChannel, msg.getChannel().getIdLong());
        dm_call(crud, 'cset, channel, litobjectarray(name := msg.getChannel().getName()));
        
        O lineConcept = dm_call(crud, 'uniqConcept, litObjectArrayAsObject(+msgID));
        dm_call(crud, 'cset, lineConcept, litobjectarray(
          text := content,
          +bot, +isPrivate,
          author := user, +channel));
        vmBus_send('newDiscordLine, lineConcept);
        
        final MessageChannel ch = e.getChannel();
        long channelID = ch.getIdLong();
        L<GazelleLine> linesInChannel = dm_discord_linesInChannel(channelID);
        
        if (bot) {
          int monologueLength = cast dm_call(dm_gazelle_linesCRUD(), 'monologueLength, channelID);
          if (monologueLength >= maxMonologue) ret;
          //Long last = get(lastPosted, l(lastPosted)-3);
          //if (last != null && now() < last+10000) ret;
        }
        
        new Matches m;
        
        temp tempSetTL(currentChannel, ch);
        temp tempSetTL(respondingTo, msg);
        
        try {
          if (swicOneOf(content, "!eval ", "!fresh ", "!real-eval", "!safe-eval ") || eqic(content, "!fresh")) {
            O safetyCheck = null;
            bool authed = dm_discord_userCanEval(userID);
            if (swic_trim(content, "!safe-eval ", m)) {
              content = "!eval " + m.rest();
              authed = false;
            }
            
            if (regexpMatches("![a-z\\-]+\\s+again", content)) {
              GazelleLine last = dm_discord_nextToLastEvalByUser(userID);
              if (last == null) ret with postInChannel(ch, "No last eval found");
              content = replaceSuffix("again", afterSpace(last.text), content);
            }
            
            if (!authed) {
              //ret with postInChannel(ch, "Sorry, you're not authed to eval");
              safetyCheck = func(S code) -> S {
                S safety = joinWithComma(getCodeFragmentSafety(code));
                if (eq(safety, 'safe)) ret "";
                S s = "Sorry. Safety level is " + safety;
                Set<S> unknown = codeAnalysis_getUnknownIdentifiers(code);
                if (nempty(unknown)) s += ". Unknown identifiers: " + joinWithComma(unknown);
                ret s;
              };
            }
            ret with dm_bot_execEvalCmd(voidfunc(S s) {
              if (s != null)
                postInChannel(ch, shorten(ifEmpty(s, [[""]]), 1000))
            }, content, +safetyCheck);
          }
          
          if (eqic(content, "!rule")) {
            LS lines = linesInChannelBy(channelID, userID);
            if (contains(nextToLast(lines), "=>"))
              content = "!rule " + nextToLast(lines);
            else {
              if (l(lines) < 3) fail("Too few lines");
              content = "!rule " + nextToNextToLast(lines) + " => " + nextToLast(lines);
            }
          }
          
          if (swicOneOf(content, m, "!rule ", "!rule\n")) {
            S s = trim(m.rest());
            print("Processing rule: " + s);
            S opt = leadingSquareBracketStuff(s);
            s = dropActuallyLeadingSquareBracketStuff(s);
            if (startsWith(s, "=>"))
              s = assertNotNull("No last line in channel", nextToLast(linesInChannelBy(channelID, userID))) + "\n" + s;
            LS comments = ll("made by user", "discord", "tokenize out with javaTok");
            if (cic(pairB(tok_splitAtDoubleArrow_pair(s)), userName)) {
              s = optCurly(userName) + " says: " + s;
              comments.add("with user name");
            }
            
            gazelle_parsePublicRuleOptions(opt, comments);
            s = replace(s, " + ", "\n+ ");
            s = jreplace1(s, "=>", "\n=>");
            s = gazelle_processSquareBracketAnnotations(s, comments);
            temp tempSetTL(dm_gazelle_addRuleWithComment_renderWithoutComments, true);
            dm_gazelle_addRuleWithComment(s, lines_rtrim(comments));
            ret with postInChannel(ch, dm_gazelle_addRuleWithComment_msg!);
          }
          
          // Check for modules
          
          LS modules = cast dm_call(dm_gazelle_modulesManager(), 'modulesForUser, "discord user " + userID);
          for (S moduleID : unnull(modules)) pcall {
            S answer = cast dm_callOpt(moduleID, 'answer, content);
            if (nempty(answer))
              postInChannel(ch, answer);
          }

          // Go into normal reasoning
          
          S purpose = null;
          bool debug = false, skipBad = true;
            
          bool change;
          do {
            change = false;
            if (swic_trim(content, "!withBad ", m)) {
              skipBad = false;
              content = m.rest(); set change;
            }
            
            if (swic_trim(content, "!preprocess ", m)) {
              purpose = 'preprocess;
              content = m.rest(); set change;
            }
          
            if (swic_trim(content, "!debug ", m)) {
              set debug;
              content = m.rest(); set change;
            }
          } while (change);
          
          print("debug=" + debug + ", content=" + content);
          
          LS preContext = takeLast(2, dropLast(collect text(linesInChannel)));
          print("preContext=" + preContext);
          
          GazelleEvalContext ctx = contextCache!;
          //ctx.engine.rules = withoutInstancesOf(RuleEngine2.SimplifyWithRule.class, ctx.engine.rules);
          L<GazelleTree> l = dm_gazelle_reasonAboutChatInput_v2(userName, content, +skipBad, +preContext, +ctx,
            badComments := mechCISet("Knock-out rule comments"),
            acceptablePurposes := nempty(purpose)
              ? litciset(purpose)
              : acceptablePurposes,
            respondingToHuman := !bot,
            +debug,
            +userID);
          
          int idx = 0;
          for (final GazelleTree t : takeFirst(10, l)) {
            final int _idx = idx++;
            
            if (eqic(t.lineType, "temporary fact")) {
              if (dm_gazelle_hasTempFact(t.line)) continue;
              print("Saving temp fact: " + t.line); 
              dm_gazelle_addTempFact(t.line, "discord msg " + msgID);
            }
            
            if (eqic(t.lineType, "eval")) {
              S code = t.rule().out;
              if (!mechSet("Gazelle | Allowed Evals").contains(code))
                print("Eval not allowed: " + code);
              else {
                S out = strOrNull(dm_javaEvalOrInterpret(code));
                if (nempty(out))
                  postInChannelWithDelete(ch, out, voidfunc(Message msg2) {
                    Gazelle_ReasoningForLine reasoning = nu(Gazelle_ReasoningForLine,
                      outMsgID := msg2.getIdLong(),
                      outText := out,
                      inMsgID := msg.getIdLong(),
                      inUserID := userID,
                      inChannelID := channelID,
                      inText := originalContent,
                      tree := t,
                      treeIndex := _idx);
                    dm_gazelle_saveReasoningForLine(reasoning);
                  });
              }
              continue;
            }
              
            fS out = tok_dropCurlyBrackets(t.line);
            print(">> " + t);
            print("POSTING: " + out);
            postInChannelWithDelete(ch, out, voidfunc(Message msg2) {
              Gazelle_ReasoningForLine reasoning = nu(Gazelle_ReasoningForLine,
                outMsgID := msg2.getIdLong(),
                outText := out,
                inMsgID := msg.getIdLong(),
                inUserID := userID,
                inChannelID := channelID,
                inText := originalContent,
                tree := t,
                treeIndex := _idx);
              dm_gazelle_saveReasoningForLine(reasoning);
            });
          }
        } catch print error {
          postInChannel(ch, exceptionToStringShort(error));
        }
      }
      
      public void onMessageReactionAdd(MessageReactionAddEvent e) pcall {
        temp enter();
        ret if !enabled || !licensed();
        MessageReaction r = e.getReaction();
        bool bot = e.getUser().isBot();
        long msgID = r.getMessageIdLong();
        add(msgsReactedTo, msgID);
        print("User " + e.getUser() + (bot ? " (bot)" : "") + " reacted to message " + msgID + " with " + r.getReactionEmote());
        if (bot) ret;

        S crud = dm_gazelle_linesCRUD();
        O lineConcept = dm_call(crud, 'uniqConcept, litObjectArrayAsObject(+msgID));
        L reactions = cast get(lineConcept, 'reactions);
        print("lineConcept=" + lineConcept);
        print("reactions=" + reactions);
        O reaction = dm_call(crud, 'nuReaction, litObjectArrayAsObject(
          user := userConcept(e.getUser()),
          emoji := r.getReactionEmote().getName(),
          created := now()));
        print("reaction=" + reaction);
          
        dm_call(crud, 'cset, lineConcept, litobjectarray(
          reactions := listPlus(reactions, reaction)));
          
        dm_discord_gatherFeedbackFromLine(dm_discord_importLine(lineConcept));
      }
    });
    
    dm_registerAs('discordBot);
    print("Started bot");
  }
  
  void cleanMeUp {
    if (bot != null) pcall {
      print("Shutting down bot");
      bot.shutdown();
      print("Bot shut down");
    }
    bot = null;
  }
  
  O userConcept(User user) {
    S crud = dm_gazelle_linesCRUD();
    O userConcept = dm_call(crud, 'uniqUser, user.getIdLong());
    dm_call(crud, 'cset, userConcept, litobjectarray(name := user.getName()));
    ret userConcept;
  }
  
  // API
  
  MessageChannel getChannel(long channelID) {
    ret bot.getTextChannelById(channelID);
  }
  
  void postInChannel(long channelID, S msg) {
    postInChannel(getChannel(channelID), msg);
  }
  
  void postInChannel(MessageChannel channel, S msg) {
    channel.sendMessage(msg).queue();
    if (l(lastPosted) > 5) popFirst(lastPosted);
    lastPosted.add(now());
  }
  
  void postInChannel(S channel, S msg) {
    long id = dm_discord_channelID(channel);
    if (id == 0) fail("Channel not found: " + channel);
    postInChannel(id, msg);
  }
  
  void postInChannelWithDelete(MessageChannel channel, S msg, VF1<Message> onPost) {
    channel.sendMessage(msg).queue(msg2 -> {
      pcallF(onPost, msg2);
      final long msgId = msg2.getIdLong();
      print("I sent msg: " + msgId);
      if (doDelete) doLater(deleteDelay, r {
        ret if contains(msgsReactedTo, msgId);
        print("Deleting msg " + msgId);
        msg2.delete().queue();
      });
    }, error -> _handleException(error));
  }
  
  void postText(S text) {
    postInChannel(currentChannel!, text);
  }
  
  void postImage(S url) {
    postImage(currentChannel!, url);
  }
  
  void postImage(S url, S title) {
    postImage(currentChannel!, url, title);
  }
  
  void postImage(MessageChannel channel, S url) {
    postImage(channel, url, "");
  }
  
  void postImage(MessageChannel channel, S url, S description) {
    channel.sendMessage(
      new EmbedBuilder()
        .setImage(absoluteURL(url))
        //.setTitle(unnull(title))
        .setDescription(unnull(description))
        .setColor(imageEmbedMysteriousLineColor)
        .build()).queue();
  }
  
  LS linesInChannelBy(long channelID, long userID) {
    ret collect text(dm_discord_linesInChannelBy(channelID, userID));
  }
  
  MessageChannel currentChannel() { ret currentChannel!; }
  Message respondingTo() { ret respondingTo!; }
  
  // for testing
  L<GazelleTree> reasonAbout(S line, O... _) {
    ret gazelle_reasonAbout(line, paramsPlus(_, ctx := contextCache!);
  }
  
  void rebuildCache {
    contextCache.fill();
  }
  
  void editMessage(long channelID, long msgID, S text) {
    getChannel(channelID).editMessageById(str(msgID), text).queue();
  }
}

Author comment

Began life as a copy of #1021781

download  show line numbers  debug dex  old transpilations   

Travelled to 8 computer(s): bhatertpkbcr, cfunsshuasjs, mqqgnosmbjvj, onxytkatvevr, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt

No comments. add comment

Snippet ID: #1022073
Snippet name: Discord Bot [Gazelle-based, v11, with new cache, old]
Eternal ID of this version: #1022073/22
Text MD5: b70fa6cd6face0623c28ca1e7751d96d
Transpilation MD5: ca1e0f067efbc3d0c3e38fe6dc5beecc
Author: stefan
Category: javax / discord
Type: JavaX source code (Dynamic Module)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2019-03-11 10:45:46
Source code size: 14946 bytes / 393 lines
Pitched / IR pitched: No / No
Views / Downloads: 323 / 955
Version history: 21 change(s)
Referenced in: [show references]