abstract sclass DynDiscordHopper > DynPrintLogAndEnabled { !include #1026292 // Discord with JDA 4.0 bool discordEnabled() { ret enabled; } bool printToModule() { true; } bool discordHopperMetaCmdsEnabled; long preferredChannelID; transient double minutesToKeepRecordedAnswers = 60.0; L recordedAnswers = syncList(); sclass RecordedAnswer { long timestamp; long questionMsgID, answerMsgID, channelID; S questionText, answerText; toString { ret stdToString(this); } } switchable transient bool verboseEvents; transient new L onDiscordStarted; transient L> onDiscordEvent = syncList(); ListenerAdapter genericListener() { ret new ListenerAdapter { public void onGenericEvent(Event e) enter { if (verboseEvents) print("Discord event: " + e); pcallFAll(onDiscordEvent, e); } }; } void onDiscordEvent(IVF1 e) { onDiscordEvent.add(e); } enhanceFrame { internalFramePopupMenuItem(f, "Start keep-alive module", rThread startKeepAliveModule); minFrameSize(f, 300, 300); } // Note: Breaking change - moved start to thread start-thread { init(); startMe(); } void startMe { dm_vmBus_onMessage_q incomingDiscordMessage(voidfunc(Map map) { if (!enabled) ret; O module = map.get('module); if (!dm_isMe(module)) ret; S s = getString content(map); long channelID = toLong(map.get('channelID)); if (channelID != 0 && preferredChannelID == 0) setField(preferredChannelID := channelID); cleanRecordedAnswers(); S answer = answer(s, map); sendReply(map, answer); }); if (!enabled) ret; print("Starting Discord"); startDiscord(); print("Started Discord"); discord.addEventListener(genericListener()); pcallFAll(onDiscordStarted); } void sendReply(Map map, S answer) { if (empty(answer)) ret; cleanRecordedAnswers(); new RecordedAnswer a; if (minutesToKeepRecordedAnswers > 0) { a.timestamp = now(); a.questionMsgID = getLong msgID(map); a.channelID = getLong channelID(map); a.questionText = getString content(map); a.answerText = answer; print("Recording answer : " + a); recordedAnswers.add(a); change(); } dm_rcall reply(map.get('module), map, answer, (IVF1) msg -> { a.answerMsgID = rcall_long getIdLong(msg); change(); }); } S answer(S s, Map map) { if (discordHopperMetaCmdsEnabled && eq(s, "!bot count")) ret lstr(dm_activeDiscordTokens()); if (eq(s, "!ip address")) ret myPublicIP(); LS tokens = extractPossibleDiscordTokens(s); for unnull (S token : tokens) { if (getLong guildID(map) != 0) ret "Send tokens only in private messages!!"; S answer = "That's a Discord token!"; if (contains(concatLists((LLS) vmBus_queryAll activeDiscordTokens()), token)) answer += " And I'm there already."; else { answer += " Jumping there!!"; dm_showNewModuleWithParams(dm_moduleLibID(), discordToken := token); } reply(map, answer); } null; } S atSelf() { ret discordAt(discordBotID); } // sometimes mentions come in like this? S atExclamSelf() { ret discordAtExclam(discordBotID); } void onDiscordStarted(Runnable r) { onDiscordStarted.add(r); } User getSelfUser() { ret discord.getSelfUser(); } // OVERRIDE ME void init { dm_watchField discordToken(r { if (nempty(discordToken) && enabled) dm_reloadModule() }); dm_reloadOnFieldChange enabled(); } void cleanRecordedAnswers { RecordedAnswer a; bool change; synchronized(recordedAnswers) { while ((a = first(recordedAnswers)) != null && elapsedMinutes_timestamp(a.timestamp) > minutesToKeepRecordedAnswers) { print("Removing old answer: " + a); recordedAnswers.remove(a); } set change; } if (change) change(); } afterVisualize { if (empty(discordToken)) containerAddFirst(buttons, jThreadedButton("Give me a token!", r { temp enter(); dm_stringFieldDialog discordToken(); })); addToContainer(buttons, jPopDownButton_noText( "Export all my data...", rThread { dm_exportStructureToTextFileDialog(module()) }, "Import all my data...", rThread { dm_importStructureFromTextFileDialog(module()) } )); } bool warnOnDelete() { true; } // API S migrateToType(S moduleLibID) { S moduleID = assertNotNull(dm_showNewModuleWithParams(moduleLibID, +discordToken)); print("Migrated to new module " + moduleID + ", disabling me"); setEnabled(false); dm_reload(); // actually disable Discord ret moduleID; } }