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

457
LINES

< > BotCompany Repo | #1022660 // Discord Bot [Gazelle-based, v14, refactoring, dev.]

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

Uses 9211K of libraries. Compilation Failed (22784L/138K).

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

Author comment

Began life as a copy of #1022488

download  show line numbers  debug dex  old transpilations   

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

No comments. add comment

Snippet ID: #1022660
Snippet name: Discord Bot [Gazelle-based, v14, refactoring, dev.]
Eternal ID of this version: #1022660/1
Text MD5: f4542ccbe5a82703fb56ae856f04a052
Transpilation MD5: 6a81a022cece6293d07930334ae6de94
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-29 15:55:47
Source code size: 16415 bytes / 457 lines
Pitched / IR pitched: No / No
Views / Downloads: 314 / 408
Referenced in: [show references]