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

451
LINES

< > BotCompany Repo | #1022193 // Discord Bot [Gazelle-based, v12, with preprocessing, old]

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

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

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

Author comment

Began life as a copy of #1022073

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: #1022193
Snippet name: Discord Bot [Gazelle-based, v12, with preprocessing, old]
Eternal ID of this version: #1022193/49
Text MD5: a8fce51f45cb7e3ecee3d12fbcdbaf3f
Transpilation MD5: a18025370f3b4947b7ecb1d6a0510bb4
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-19 10:26:14
Source code size: 16943 bytes / 451 lines
Pitched / IR pitched: No / No
Views / Downloads: 427 / 1468
Version history: 48 change(s)
Referenced in: [show references]