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).

1  
!7
2  
3  
set flag DynModule.
4  
5  
cmodule DiscordBot > DynPrintLogAndEnabled {
6  
  transient JDA bot;
7  
  transient new Set<Long> msgsReactedTo;
8  
  transient double deleteDelay = 60.0;
9  
  transient bool doDelete;
10  
  transient int maxMonologue = 5;
11  
  transient Color imageEmbedMysteriousLineColor = colorFromHex("36393f");
12  
13  
  transient new InheritableThreadLocal<MessageChannel> currentChannel;
14  
  transient new InheritableThreadLocal<Message> respondingTo;
15  
  
16  
  transient new L<Long> lastPosted;
17  
  transient GazelleContextCache_v2 contextCache;
18  
  
19  
  transient Set<S> acceptablePurposes = litciset("");
20  
21  
  start {
22  
    logModuleOutput();
23  
    dm_useLocalMechListCopies();
24  
    
25  
    print("Making cache");
26  
    time "Made cache" {
27  
      contextCache = new GazelleContextCache_v2(this);
28  
      ownResource(contextCache.listenToMessages());
29  
      contextCache.fill();
30  
    }
31  
    
32  
    // TODO: log JDA output
33  
    bot = discordBot(new ListenerAdapter {
34  
      public void onMessageUpdate(MessageUpdateEvent e) pcall {
35  
        temp enter();
36  
        ret if !enabled || !licensed();
37  
        
38  
        long msgID = e.getMessage().getIdLong();
39  
        S content = e.getMessage().getContentRaw();
40  
        O lineConcept = dm_discord_lineForMsgID_unimported(msgID);
41  
        S rendered = msgID + ": " + vm_cleanForPrint(content);
42  
        if (lineConcept == null)
43  
          ret with print("Weird: Message edited, but not found: " + rendered);
44  
        call(lineConcept, '_setField, editedText := content);
45  
        print("Message edited: " + rendered);
46  
      }
47  
      
48  
      public void onMessageReceived(MessageReceivedEvent e) pcall {
49  
        temp enter();
50  
        ret if !enabled || !licensed();
51  
        
52  
        bool bot = e.getAuthor().isBot();
53  
        long msgID = e.getMessage().getIdLong();
54  
        long userID = e.getAuthor().getIdLong();
55  
        Member member = e.getMember();
56  
        S userName = member == null ? null : member.getNickname(); // the changeable nick name - null sometimes?
57  
        if (userName == null && member != null) userName = member.getEffectiveName();
58  
        if (userName == null) userName = e.getAuthor().getName();
59  
        
60  
        final Message msg = e.getMessage();
61  
        
62  
        print("Channel type: " + e.getChannelType());
63  
        bool isPrivate = e.getChannelType() == ChannelType.PRIVATE;
64  
        S content = trim(msg.getContentRaw());
65  
        print("Msg from " + userName + ": " + vm_cleanForPrint(content));
66  
        if (empty(content)) ret;
67  
        fS originalContent = content;
68  
69  
        O user = userConcept(e.getAuthor());
70  
        
71  
        S crud = dm_gazelle_linesCRUD();
72  
        O channel = dm_call(crud, 'uniqChannel, msg.getChannel().getIdLong());
73  
        dm_call(crud, 'cset, channel, litobjectarray(name := msg.getChannel().getName()));
74  
        
75  
        O lineConcept = dm_call(crud, 'uniqConcept, litObjectArrayAsObject(+msgID));
76  
        dm_call(crud, 'cset, lineConcept, litobjectarray(
77  
          text := content,
78  
          +bot, +isPrivate,
79  
          author := user, +channel));
80  
        vmBus_send('newDiscordLine, lineConcept);
81  
        
82  
        final MessageChannel ch = e.getChannel();
83  
        long channelID = ch.getIdLong();
84  
        L<GazelleLine> linesInChannel = dm_discord_linesInChannel(channelID);
85  
        
86  
        if (bot) {
87  
          int monologueLength = cast dm_call(dm_gazelle_linesCRUD(), 'monologueLength, channelID);
88  
          if (monologueLength >= maxMonologue) ret;
89  
          //Long last = get(lastPosted, l(lastPosted)-3);
90  
          //if (last != null && now() < last+10000) ret;
91  
        }
92  
        
93  
        new Matches m;
94  
        
95  
        temp tempSetTL(currentChannel, ch);
96  
        temp tempSetTL(respondingTo, msg);
97  
        
98  
        try {
99  
          if (swicOneOf(content, "!eval ", "!fresh ", "!real-eval", "!safe-eval ") || eqic(content, "!fresh")) {
100  
            O safetyCheck = null;
101  
            bool authed = dm_discord_userCanEval(userID);
102  
            if (swic_trim(content, "!safe-eval ", m)) {
103  
              content = "!eval " + m.rest();
104  
              authed = false;
105  
            }
106  
            
107  
            if (regexpMatches("![a-z\\-]+\\s+again", content)) {
108  
              GazelleLine last = dm_discord_nextToLastEvalByUser(userID);
109  
              if (last == null) ret with postInChannel(ch, "No last eval found");
110  
              content = replaceSuffix("again", afterSpace(last.text), content);
111  
            }
112  
            
113  
            if (!authed) {
114  
              //ret with postInChannel(ch, "Sorry, you're not authed to eval");
115  
              safetyCheck = func(S code) -> S {
116  
                S safety = joinWithComma(getCodeFragmentSafety(code));
117  
                if (eq(safety, 'safe)) ret "";
118  
                S s = "Sorry. Safety level is " + safety;
119  
                Set<S> unknown = codeAnalysis_getUnknownIdentifiers(code);
120  
                if (nempty(unknown)) s += ". Unknown identifiers: " + joinWithComma(unknown);
121  
                ret s;
122  
              };
123  
            }
124  
            ret with dm_bot_execEvalCmd(voidfunc(S s) {
125  
              if (s != null)
126  
                postInChannel(ch, shorten(ifEmpty(s, [[""]]), 1000))
127  
            }, content, +safetyCheck);
128  
          }
129  
          
130  
          if (eqic(content, "!rule")) {
131  
            LS lines = linesInChannelBy(channelID, userID);
132  
            if (contains(nextToLast(lines), "=>"))
133  
              content = "!rule " + nextToLast(lines);
134  
            else {
135  
              if (l(lines) < 3) fail("Too few lines");
136  
              content = "!rule " + nextToNextToLast(lines) + " => " + nextToLast(lines);
137  
            }
138  
          }
139  
          
140  
          if (swicOneOf(content, m, "!rule ", "!rule\n")) {
141  
            S s = trim(m.rest());
142  
            print("Processing rule: " + s);
143  
            S opt = leadingSquareBracketStuff(s);
144  
            s = dropActuallyLeadingSquareBracketStuff(s);
145  
            if (startsWith(s, "=>"))
146  
              s = assertNotNull("No last line in channel", nextToLast(linesInChannelBy(channelID, userID))) + "\n" + s;
147  
            LS comments = ll("made by user", "discord", "tokenize out with javaTok");
148  
            if (cic(pairB(tok_splitAtDoubleArrow_pair(s)), userName)) {
149  
              s = optCurly(userName) + " says: " + s;
150  
              comments.add("with user name");
151  
            }
152  
            
153  
            gazelle_parsePublicRuleOptions(opt, comments);
154  
            s = replace(s, " + ", "\n+ ");
155  
            s = jreplace1(s, "=>", "\n=>");
156  
            s = gazelle_processSquareBracketAnnotations(s, comments);
157  
            temp tempSetTL(dm_gazelle_addRuleWithComment_renderWithoutComments, true);
158  
            dm_gazelle_addRuleWithComment(s, lines_rtrim(comments));
159  
            ret with postInChannel(ch, dm_gazelle_addRuleWithComment_msg!);
160  
          }
161  
          
162  
          // Check for modules
163  
          
164  
          LS modules = cast dm_call(dm_gazelle_modulesManager(), 'modulesForUser, "discord user " + userID);
165  
          for (S moduleID : unnull(modules)) pcall {
166  
            S answer = cast dm_callOpt(moduleID, 'answer, content);
167  
            if (nempty(answer))
168  
              postInChannel(ch, answer);
169  
          }
170  
171  
          // Go into normal reasoning
172  
          
173  
          S purpose = null;
174  
          bool debug = false, skipBad = true;
175  
            
176  
          bool change;
177  
          do {
178  
            change = false;
179  
            if (swic_trim(content, "!withBad ", m)) {
180  
              skipBad = false;
181  
              content = m.rest(); set change;
182  
            }
183  
            
184  
            if (swic_trim(content, "!preprocess ", m)) {
185  
              purpose = 'preprocess;
186  
              content = m.rest(); set change;
187  
            }
188  
          
189  
            if (swic_trim(content, "!debug ", m)) {
190  
              set debug;
191  
              content = m.rest(); set change;
192  
            }
193  
          } while (change);
194  
          
195  
          print("debug=" + debug + ", content=" + content);
196  
          
197  
          LS preContext = takeLast(2, dropLast(collect text(linesInChannel)));
198  
          print("preContext=" + preContext);
199  
          
200  
          GazelleEvalContext ctx = contextCache!;
201  
          //ctx.engine.rules = withoutInstancesOf(RuleEngine2.SimplifyWithRule.class, ctx.engine.rules);
202  
          L<GazelleTree> l = dm_gazelle_reasonAboutChatInput_v2(userName, content, +skipBad, +preContext, +ctx,
203  
            badComments := mechCISet("Knock-out rule comments"),
204  
            acceptablePurposes := nempty(purpose)
205  
              ? litciset(purpose)
206  
              : acceptablePurposes,
207  
            respondingToHuman := !bot,
208  
            +debug,
209  
            +userID);
210  
          
211  
          int idx = 0;
212  
          for (final GazelleTree t : takeFirst(10, l)) {
213  
            final int _idx = idx++;
214  
            
215  
            if (eqic(t.lineType, "temporary fact")) {
216  
              if (dm_gazelle_hasTempFact(t.line)) continue;
217  
              print("Saving temp fact: " + t.line); 
218  
              dm_gazelle_addTempFact(t.line, "discord msg " + msgID);
219  
            }
220  
            
221  
            if (eqic(t.lineType, "eval")) {
222  
              S code = t.rule().out;
223  
              if (!mechSet("Gazelle | Allowed Evals").contains(code))
224  
                print("Eval not allowed: " + code);
225  
              else {
226  
                S out = strOrNull(dm_javaEvalOrInterpret(code));
227  
                if (nempty(out))
228  
                  postInChannelWithDelete(ch, out, voidfunc(Message msg2) {
229  
                    Gazelle_ReasoningForLine reasoning = nu(Gazelle_ReasoningForLine,
230  
                      outMsgID := msg2.getIdLong(),
231  
                      outText := out,
232  
                      inMsgID := msg.getIdLong(),
233  
                      inUserID := userID,
234  
                      inChannelID := channelID,
235  
                      inText := originalContent,
236  
                      tree := t,
237  
                      treeIndex := _idx);
238  
                    dm_gazelle_saveReasoningForLine(reasoning);
239  
                  });
240  
              }
241  
              continue;
242  
            }
243  
              
244  
            fS out = tok_dropCurlyBrackets(t.line);
245  
            print(">> " + t);
246  
            print("POSTING: " + out);
247  
            postInChannelWithDelete(ch, out, voidfunc(Message msg2) {
248  
              Gazelle_ReasoningForLine reasoning = nu(Gazelle_ReasoningForLine,
249  
                outMsgID := msg2.getIdLong(),
250  
                outText := out,
251  
                inMsgID := msg.getIdLong(),
252  
                inUserID := userID,
253  
                inChannelID := channelID,
254  
                inText := originalContent,
255  
                tree := t,
256  
                treeIndex := _idx);
257  
              dm_gazelle_saveReasoningForLine(reasoning);
258  
            });
259  
          }
260  
        } catch print error {
261  
          postInChannel(ch, exceptionToStringShort(error));
262  
        }
263  
      }
264  
      
265  
      public void onMessageReactionAdd(MessageReactionAddEvent e) pcall {
266  
        temp enter();
267  
        ret if !enabled || !licensed();
268  
        MessageReaction r = e.getReaction();
269  
        bool bot = e.getUser().isBot();
270  
        long msgID = r.getMessageIdLong();
271  
        add(msgsReactedTo, msgID);
272  
        print("User " + e.getUser() + (bot ? " (bot)" : "") + " reacted to message " + msgID + " with " + r.getReactionEmote());
273  
        if (bot) ret;
274  
275  
        S crud = dm_gazelle_linesCRUD();
276  
        O lineConcept = dm_call(crud, 'uniqConcept, litObjectArrayAsObject(+msgID));
277  
        L reactions = cast get(lineConcept, 'reactions);
278  
        print("lineConcept=" + lineConcept);
279  
        print("reactions=" + reactions);
280  
        O reaction = dm_call(crud, 'nuReaction, litObjectArrayAsObject(
281  
          user := userConcept(e.getUser()),
282  
          emoji := r.getReactionEmote().getName(),
283  
          created := now()));
284  
        print("reaction=" + reaction);
285  
          
286  
        dm_call(crud, 'cset, lineConcept, litobjectarray(
287  
          reactions := listPlus(reactions, reaction)));
288  
          
289  
        dm_discord_gatherFeedbackFromLine(dm_discord_importLine(lineConcept));
290  
      }
291  
    });
292  
    
293  
    dm_registerAs('discordBot);
294  
    print("Started bot");
295  
  }
296  
  
297  
  void cleanMeUp {
298  
    if (bot != null) pcall {
299  
      print("Shutting down bot");
300  
      bot.shutdown();
301  
      print("Bot shut down");
302  
    }
303  
    bot = null;
304  
  }
305  
  
306  
  O userConcept(User user) {
307  
    S crud = dm_gazelle_linesCRUD();
308  
    O userConcept = dm_call(crud, 'uniqUser, user.getIdLong());
309  
    dm_call(crud, 'cset, userConcept, litobjectarray(name := user.getName()));
310  
    ret userConcept;
311  
  }
312  
  
313  
  // API
314  
  
315  
  MessageChannel getChannel(long channelID) {
316  
    ret bot.getTextChannelById(channelID);
317  
  }
318  
  
319  
  void postInChannel(long channelID, S msg) {
320  
    postInChannel(getChannel(channelID), msg);
321  
  }
322  
  
323  
  void postInChannel(MessageChannel channel, S msg) {
324  
    channel.sendMessage(msg).queue();
325  
    if (l(lastPosted) > 5) popFirst(lastPosted);
326  
    lastPosted.add(now());
327  
  }
328  
  
329  
  void postInChannel(S channel, S msg) {
330  
    long id = dm_discord_channelID(channel);
331  
    if (id == 0) fail("Channel not found: " + channel);
332  
    postInChannel(id, msg);
333  
  }
334  
  
335  
  void postInChannelWithDelete(MessageChannel channel, S msg, VF1<Message> onPost) {
336  
    channel.sendMessage(msg).queue(msg2 -> {
337  
      pcallF(onPost, msg2);
338  
      final long msgId = msg2.getIdLong();
339  
      print("I sent msg: " + msgId);
340  
      if (doDelete) doLater(deleteDelay, r {
341  
        ret if contains(msgsReactedTo, msgId);
342  
        print("Deleting msg " + msgId);
343  
        msg2.delete().queue();
344  
      });
345  
    }, error -> _handleException(error));
346  
  }
347  
  
348  
  void postText(S text) {
349  
    postInChannel(currentChannel!, text);
350  
  }
351  
  
352  
  void postImage(S url) {
353  
    postImage(currentChannel!, url);
354  
  }
355  
  
356  
  void postImage(S url, S title) {
357  
    postImage(currentChannel!, url, title);
358  
  }
359  
  
360  
  void postImage(MessageChannel channel, S url) {
361  
    postImage(channel, url, "");
362  
  }
363  
  
364  
  void postImage(MessageChannel channel, S url, S description) {
365  
    channel.sendMessage(
366  
      new EmbedBuilder()
367  
        .setImage(absoluteURL(url))
368  
        //.setTitle(unnull(title))
369  
        .setDescription(unnull(description))
370  
        .setColor(imageEmbedMysteriousLineColor)
371  
        .build()).queue();
372  
  }
373  
  
374  
  LS linesInChannelBy(long channelID, long userID) {
375  
    ret collect text(dm_discord_linesInChannelBy(channelID, userID));
376  
  }
377  
  
378  
  MessageChannel currentChannel() { ret currentChannel!; }
379  
  Message respondingTo() { ret respondingTo!; }
380  
  
381  
  // for testing
382  
  L<GazelleTree> reasonAbout(S line, O... _) {
383  
    ret gazelle_reasonAbout(line, paramsPlus(_, ctx := contextCache!);
384  
  }
385  
  
386  
  void rebuildCache {
387  
    contextCache.fill();
388  
  }
389  
  
390  
  void editMessage(long channelID, long msgID, S text) {
391  
    getChannel(channelID).editMessageById(str(msgID), text).queue();
392  
  }
393  
}

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: 324 / 957
Version history: 21 change(s)
Referenced in: [show references]