abstract sclass DynDiscordHopper > DynPrintLogAndEnabled { !include #1023434 // Discord 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; } switchable transient bool verboseEvents; transient new L onDiscordStarted; transient L> onDiscordEvent = syncList(); ListenerAdapter genericListener = new { public void onGenericEvent(Event e) enter { if (verboseEvents) print("Discord event: " + e); pcallFAll(onDiscordEvent, e); } }; enhanceFrame { internalFramePopupMenuItem(f, "Start keep-alive module", rThread startKeepAliveModule); } // 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); }); startDiscord(); discord.addEventListener(genericListener); print("onDiscordStarted=" + onDiscordStarted); 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; 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()); 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); } void onDiscordStarted(Runnable r) { onDiscordStarted.add(r); } User getSelfUser() { ret discord.getSelfUser(); } // OVERRIDE ME void init {} void cleanRecordedAnswers { RecordedAnswer a; bool change; synchronized(recordedAnswers) { while ((a = first(recordedAnswers)) != null && elapsedMinutes_timestamp(a.timestamp) > minutesToKeepRecordedAnswers) popFirst(recordedAnswers); set change; } if (change) change(); } // 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; } }