预警 (Forewarning)
This article won’t only be talking about Discord bots and it’s not intended to be a proper introduction to them. If you’re looking for that, or even how to create your own bot, check out the https://discordjs.guide/ or take a look at some responses in this Quora thread.
本文不仅会讨论Discord机器人,也无意对它们进行适当的介绍。 如果您正在寻找它,甚至是如何创建自己的机器人,请查看https://discordjs.guide/或在Quora线程中查看一些响应。
To understand this article fully, you’ll need some understanding of JavaScript, MongoDB, Mongoose ODM, Node.js, Promises and Async/Await, and some general programming knowledge to understand some of the syntax used.
要完全理解本文,您需要对JavaScript,MongoDB,Mongoose ODM,Node.js,Promises和Async / Await有所了解,并需要一些常规编程知识才能了解所使用的某些语法。
I won’t really touch base on the set up of MongoDB and Mongoose either. Here are some links if you need to learn a bit more:
我也不会真正基于MongoDB和Mongoose的设置。 如果您需要了解更多信息,请参见以下链接:
Learn more about JavaScript
进一步了解JavaScript
Learn more about MongoDB and Mongoose ODM
了解有关MongoDB和Mongoose ODM的更多信息
To get started, I’m going to talk in-depth about a couple of issues I had with the creation of Jeetbot.
首先,我将深入讨论创建Jeetbot时遇到的几个问题。
It’s a long read so I put a tl;dr at the end.
读了很长的书,所以我在后面加上了tl; dr。
那么,有什么问题呢? (So, What’s The Issue?)
In my journey to creating a useful Discord bot for discord users and myself, there came a point where I was retrieving data from my MongoDB database too many times. In a happy, land-of-the-free type of internet, all the reads/writes would cost me nothing. But transferring data back and forth on the internet costs money at times, and I wanted to scale this bot properly, so I needed to save some of the data locally, within the bot.
在为不和谐的用户和我自己创建有用的Discord bot的过程中,有一次我从MongoDB数据库中检索数据的次数太多了。 在快乐,免费的互联网类型中,所有读/写操作都不会花我任何钱。 但是在Internet上来回传输数据有时会花费金钱,而且我想适当地扩展此机器人,因此我需要在该机器人内部本地保存一些数据。
One of the things that the bot has to keep an eye on are user inputs that change the database. This means that users are actively mutating/changing certain options and this is happening asynchronously with the rest of the other Discord servers the bot was on.
机器人必须注意的一件事是更改数据库的用户输入。 这意味着用户正在积极地更改/更改某些选项,并且该行为与漫游器所在的其他其余Discord服务器异步发生。
听起来不太难解决... (That doesn’t sound too hard to fix…)
I thought I needed to set up a hash map data structure when the bot initializes and makes changes to the local cache when database changes occur. But it wasn’t that easy. Let me describe below what I had encountered in my process trying this out.
我以为我需要在Bot初始化时设置哈希映射数据结构,并在发生数据库更改时对本地缓存进行更改。 但这不是那么容易。 让我在下面描述在尝试过程中遇到的问题。
I have a command handler that takes in splits up the message coming from a Discord user and recognizes it as a command:
我有一个命令处理程序,用于拆分来自Discord用户的消息并将其识别为命令:
client.on("message", async (msg) => {
commandHandler(msg);
});
When a message is sent to the bot, it runs the message through the commandHandler
then ends up where that specific command would be. Although, it’s doing asynchronously for every message sent.
当消息发送到机器人时,它将通过commandHandler
运行消息,然后终止于该特定命令所在的位置。 虽然,它对发送的每个消息都是异步进行的。
So the command flow looks like this:
因此,命令流如下所示:
index.js → commandHandler → specific command.
index.js→commandHandler→特定命令。
When I initially set up the hashmap, ie new Map()
, it would have the following data inside:
当我最初设置哈希new Map()
即new Map()
,其中将包含以下数据:
{
guild.id: guildDataObject
}
Note: I use “guild” and “discord server” interchangeably.
注意:我可以交替使用“行会”和“ Discord服务器”。
But when I sent my Map
object containing all the guild information, it came back incorrect. The data did not return on time, or it was stuck on the stack waiting to unload the promise.
但是,当我发送包含所有行会信息的Map
对象时,该对象返回错误。 数据没有按时返回,或者被卡在堆栈上等待卸载承诺。
Note: it’s currently assigned as j.edit instead of j.welcomeEdit in the 1.1.2 version of Jeetbot.
注意:在1.1.2版本的Jeetbot中,它当前被分配为j.edit而不是j.welcomeEdit 。
For example:
例如:
Discord moderator types in
j.welcomeEdit
in the discord channel.Discord频道中
j.welcomeEdit
中的Discord主持人类型。- Bot recognizes the command. Bot会识别命令。
- Bot sends the user a prompt to enter in new information for the welcome message. Bot向用户发送提示,要求输入新信息以显示欢迎消息。
- Bot awaits for a new welcome message to come, but other messages and new commands are stacking on top. Bot等待新的欢迎消息到来,但是其他消息和新命令则堆积在最上面。
- Discord moderator sends a response to change the welcome message. 不和谐主持人发送响应以更改欢迎消息。
- Bot accepts the message but the Promise does not return on time to use immediately. It’s in the queue awaiting other promises to finish. Bot接受了该消息,但Promise并未准时返回以立即使用。 它正在排队等待其他诺言完成。
❌ Error: Not Returning The Right Welcome Message.
❌
Error: Not Returning The Right Welcome Message.
❌
In order for the bot to actually use the new message, I had to call j.welcomeEdit
several times before the new welcome message would come back to its rightful place.
为了使bot实际使用新消息,我必须多次调用j.welcomeEdit
,然后新的欢迎消息才返回其应有的位置。
Promises were stacking up and nothing was coming back on time. In a production environment, there’s no way this would be acceptable.
承诺堆积如山,一切都没有回到准时。 在生产环境中,这是无法接受的。
我有两个主要问题 (I had two major issues)
- Making too many reads/writes to the database, every time a message was sent. 每次发送消息时,都会对数据库进行过多的读/写操作。
My bot would not return the local cache properly so it would not update immediately when the user updates their data.
我的机器人无法正确返回本地缓存,因此当用户更新其数据时,它不会立即更新。
My gut feeling was that I need something like ReactJS’s useState()
. Since I wasn’t making a web app within this bot, the only thing that came to mind was giving Redux.js a try.
我的直觉是我需要像ReactJS的useState()
这样的东西。 由于我不是在此bot内创建Web应用程序,因此唯一想到的就是尝试Redux.js。
Redux.js进行救援 (Redux.js to the Rescue)
One of my mentors, who taught me how to code at Code Chrysalis, once told me, “there will come a day when you truly need to use Redux.js.”
我的一位导师曾经告诉我,“我将有一天真正需要使用Redux.js。”他曾在Code Chrysalis教我如何编码。
Granted, I’ve used ReactJS and Redux.js together. I’m familiar with Hooks and handling state within React applications. But this bot runs completely separate from the web page the Jeetbot team had built for it. So, taking the time to look at articles and architecture of how React apps are built within JavaScript, I felt I might have to do something similar with Redux.js, handling state in the same manner.
当然,我一起使用了ReactJS和Redux.js。 我熟悉Hooks和在React应用程序中处理状态。 但是,该机器人的运行与Jeetbot团队为其构建的网页完全不同。 因此,花时间去研究关于如何在JavaScript中构建React应用程序的文章和体系结构,我觉得我可能必须对Redux.js做类似的事情,以相同的方式处理状态。
Redux is great for handling a global state that deals with constant mutation and asynchronicity.
Redux非常适合处理处理持续不断的变异和异步性的全局状态。
When I mapped out how I wanted the bot to work, this is what the control flow ended up looking like:
当我确定了我希望机器人如何工作时,控制流最终看起来像这样:
My entry to the Jeetbot starts off at index.js. It will initialize the redux store and store all the existing data on MongoDB in the store.
我进入Jeetbot是从index.js开始的。 它将初始化redux存储并将所有现有数据存储在该存储中的MongoDB上。
After Jeetbot starts up, it starts to listen for events or commands sent on Discord Servers where Jeetbot is on different servers. All events are currently handled within index.js and all commands will go down to the command handler, which then goes to commands.
Jeetbot启动后,它开始侦听Jeetbot在不同服务器上的Discord服务器上发送的事件或命令。 当前所有事件都在index.js中处理,所有命令将进入命令处理程序,然后进入命令处理程序。
Discord Servers → Index.js → CommandHandler → Commands
Discord服务器→Index.js→CommandHandler→命令
When a command is triggered to make a change to the current data located within the store, the local store is updated and so will the MongoDB database.
触发命令以更改存储中的当前数据时,本地存储将更新,MongoDB数据库也会更新。
太好了-我们有一个可行的想法 (Great — We Have a Working Idea)
But how do I write the code needed to make this happen? We need to understand how Redux works. It’s not like I was making a React-Redux website, nor am I trying to make a little To-Do app that needed state to render and show on the screen.
但是,我该如何编写实现此目标所需的代码? 我们需要了解Redux的工作方式。 并不是我在创建React-Redux网站,也不是在尝试制作一个需要状态才能在屏幕上显示和显示的To-Do应用程序。
After lots of pain and sweat, I found a solution with the Redux Toolkit — the official, opinionated, batteries-included toolset for efficient Redux development.
经过大量的痛苦和汗水,我找到了Redux Toolkit的解决方案-官方的,自以为是的,包含电池的工具集,用于高效的Redux开发。
If you know Pokemon cards, it’s essentially a pre-set booster pack with cards you know that’ll be useful if you use them the right way.
如果您知道Pokemon卡,那么它实际上是一个预先设定的增强包,其中包含您知道的卡,如果正确使用它们将很有用。
高阶检视 (High-Level View)
Redux逻辑 (The Redux logic)
guildsSlice.js
— This is where we handle all mutation logic. From here, I create some general selectors and reducers to import to the rest of the program. You’ll see how I use them shortly.
guildsSlice.js
—这是我们处理所有变异逻辑的地方。 从这里,我创建一些常规的选择器和缩减器,以导入到程序的其余部分。 您很快就会看到我如何使用它们。
I’m following the “Ducks” methodology Redux suggests. (Essentially putting all redux logic for one feature into one file).
我遵循Redux建议的“ Ducks ”方法。 (本质上将一个功能的所有redux逻辑放入一个文件中)。
store.js — This is where we configure our store. It will include our reducers and any middleware we intend to use.
store.js-这是我们配置商店的地方。 它将包括我们的reducer和我们打算使用的任何中间件。
guildsSlice.js (guildsSlice.js)
const { createSlice, createEntityAdapter } = require('@reduxjs/toolkit');
const guildsAdapter = createEntityAdapter();
const guildsSelector = guildsAdapter.getSelectors(state => state.guilds)
const guildsSlice = createSlice({
name: 'guilds',
initialState: guildsAdapter.getInitialState(),
reducers: {
guildAdded(state, action) {
const {_id} = action.payload;
action.payload.id = _id;
guildsAdapter.addOne(state, action.payload);
},
guildRemoved(state, action) {
const {_id} = action.payload;
action.payload.id = _id;
guildsAdapter.removeOne(state, action.payload);
},
guildWelcomeMessageUpdated(state, action) {
const {_id, welcomeMessage } = action.payload;
const guild = state.entities[_id];
if(guild) {
if(guild.welcomeMessage.messageInfo !== welcomeMessage.messageInfo) {
guild.welcomeMessage.messageInfo = welcomeMessage.messageInfo;
}
if(guild.welcomeMessage.welcomeChannel !== welcomeMessage.welcomeChannel) {
guild.welcomeMessage.welcomeChannel = welcomeMessage.welcomeChannel;
}
}
},
}
});
const {
guildAdded,
guildRemoved,
guildWelcomeMessageUpdated,
} = guildsSlice.actions
module.exports = {
guildsSelector,
guildsSlice,
guildAdded,
guildRemoved,
guildWelcomeMessageUpdated,
}
The Redux Toolkit comes with nifty functions such as createEntityAdapter
and createSlice
. Both functions make it easier to create proper Redux logic. We need both functions here so I’ll be talking about them in unison.
Redux Toolkit带有漂亮的功能,例如createEntityAdapter
和createSlice
。 这两个功能使创建适当的Redux逻辑变得更加容易。 我们在这里需要两个函数,因此我将统一讨论它们。
Essentially, createEntityAdapter
comes with CRUD operations (Create, Read, Update, Delete) for mutations within your Redux Store and it comes with Selector
s so that you can grab the data within the store.
本质上, createEntityAdapter
带有Redux Store中用于突变的CRUD操作(创建,读取,更新,删除),并且带有Selector
因此您可以在存储中获取数据。
First, I created an instance or a copy of createEntityAdapter
and call it guildsAdapter
. Now, guildsAdapter
has access to other functions within createEntityAdapter
.
首先,我创建了一个实例或createEntityAdapter
的副本,并将其guildsAdapter
。 现在, guildsAdapter
可以访问createEntityAdapter
其他功能。
The methods that I’ll be using are the following:
我将使用的方法如下:
guildsAdapter.getSelectors()
guildsAdapter. getSelectors()
guildsAdapter.getInitialState()
guildsAdapter. getInitialState()
guildsAdapter.addOne()
guildsAdapter. addOne()
guildsAdapter.removeOne()
guildsAdapter. removeOne()
On line 4 of guildsSlice.js I create my own guildsSelector
which accesses specific data within the store. I’ll export this function out of guildsSlice.js and import it later into index.js for use.
在guildsSlice.js的第4行上,我创建了自己的guildsSelector
,它可以访问商店中的特定数据。 我将从guildsSlice.js中导出此函数,然后将其导入index.js中以供使用。
Take a look at line 6, where I make a const guildsSlice = createSlice({});
. The createSlice
function needs the following inputs to make it work properly:
看一下第6行,在这里我做一个const guildsSlice = createSlice({});
。 createSlice
函数需要以下输入才能使其正常工作:
// A name, used in action typesname: string,// The initial state for the reducerinitialState: any,// An object of "case reducers".
// Key names will be used to generate actions.reducers: Object<string, ReducerFunction | ReducerAndPrepareObject>
Now I have access to all the functions within the createEntityAdapter
through the guildsAdapter.
现在,我可以通过guildsAdapter.
访问createEntityAdapter
所有功能guildsAdapter.
name: guilds
— we’re dealing with discord servers, and they’re called guilds within Discord.js.
name : guilds
-我们正在处理不和谐服务器,在Discord.js中它们被称为公会。
initialState: guildsAdapter.getInitialState()
— remember that getInitialState()
method I talked about earlier. It creates the following data structure for us:
initialState : guildsAdapter.getInitialState()
-请记住我之前提到的getInitialState()
方法。 它为我们创建了以下数据结构:
{
// The unique IDs of each item. Must be strings or numbers
ids: [],
// A lookup table mapping entity IDs to the corresponding entity
// objects
entities: {}
}
For every id
, it will refer to an entity.id
and has an object connected to that entity.id
.
对于每个id
,它将引用一个entity.id
并具有一个连接到该entity.id
的对象。
This is what the data will look like when I use store.getState()
in index.js:
当我在index.js中使用store.getState()
时,数据将如下所示:
{
guilds: {
ids: [
'276775249180360704', // discord server ID
'428518913144520704',
// additional IDs
],
entities: {
'276775249180360704': [Object], // discord server ID
'428518913144520704': [Object],
// additional { IDs: Objects }
}
}
}
Next, for the createSlice
object, we need our reducers.
接下来,对于createSlice
对象,我们需要我们的reducers 。
guildAdded()
, guildRemoved()
, and guildWelcomeMessageUpdated()
take the current state and an action. Within the action, there is an action.payload
that is essentially anything we send when we call the function elsewhere. (More on this later)
guildAdded()
, guildRemoved()
和guildWelcomeMessageUpdated()
采取当前状态并采取措施。 在该动作中,有一个action.payload
本质上是我们在其他地方调用该函数时发送的任何内容。 (稍后会详细介绍)
Within guildAdded()
and guildRemoved()
is the following:
在guildAdded()
和guildRemoved()
中:
I destructure the
_id
from the payload.我从有效载荷中解构了
_id
。I reassign it as
action.payload.id = _id
.我将其重新分配为
action.payload.id = _id
。I use
guildsAdapter.addOne
(or.removeOne
) so that the store updates if there is a new Discord Server added to the database (Another user adds Jeetbot) or a Discord Server that is deleted off the database (Someone kicks Jeetbot off their server).我使用
guildsAdapter.addOne
(或.removeOne
),以便在数据库中添加了新的Discord Server(另一个用户添加Jeetbot)或从数据库中删除的Discord Server(有人将Jeetbot从其服务器.removeOne
)时,商店进行了更新。 。
Within guildWelcomeMessageUpdated()
, I also de-structure the _id
and look for the discord server referenced in const guild = state.entities[_id]
.
在guildWelcomeMessageUpdated()
,我还guildWelcomeMessageUpdated()
了_id
并查找const guild = state.entities[_id]
引用的不一致服务器。
Afterward, I apply my logic to update the guild’s welcome message within the store.
之后,我将运用自己的逻辑来更新商店中公会的欢迎消息。
Lastly, we’ll do some more de-structuring for our actions so we can use them in our files later. Let’s module.exports
everything and we’re good to go:
最后,我们将对操作进行更多的分解,以便以后可以在文件中使用它们。 让我们module.exports
一切,我们很好:
const {
guildAdded,
guildRemoved,
guildWelcomeMessageUpdated,
} = guildsSlice.actions // destructure the actions to use latermodule.exports = {
guildsSelector, // our selector to access the data
guildsSlice, // our slice adding to the config of the store
guildAdded,
guildRemoved,
guildWelcomeMessageUpdated,
}
store.js (store.js)
const { configureStore, getDefaultMiddleware } = require('@reduxjs/toolkit');
const { guildsSlice } = require('./guildsSlice');
module.exports = configureStore({
reducer: {
guilds: guildsSlice.reducer
},
middleware: getDefaultMiddleware({
immutableCheck: false,
serializableCheck: false,
}),
devTools: false,
});
This is where we set up our configuration. Most of the architecture and logic work was done in the guildsSlice.js. When we set up the store in our index.js, it will have this store configuration every time.
这是我们设置配置的地方。 大多数架构和逻辑工作都是在guildSSlice.js中完成的。 当我们在index.js中设置商店时,每次都会有该商店配置。
Let's dissect this.
让我们对此进行剖析。
From the Redux Toolkit, I get theconfigureStore
and getDefaultMiddleware
functions. I import guildsSlice
from our guildsSlice.js file.
从Redux Toolkit中,我获得了configureStore
和getDefaultMiddleware
函数。 我从guildsSlice.js文件导入guildsSlice
。
The most important part here is that we are setting up our reducers properly for the store:
这里最重要的部分是我们正在为商店正确设置减速器:
module.exports = configureStore({
reducer: {
guilds: guildsSlice.reducer
},
// other code
});
We’re exporting the reducers from guildsSlice.js here to be recognized within the store when the store is initialized in Index.js.
我们将从guildsSlice.js导出减速器,以便在Index.js中初始化商店时在商店中识别它们。
guildsSlice.js → store.js → index.js is the flow.
guildsSlice.js→store.js→index.js是流程。
For developmental purposes, immutableCheck
, serializableCheck
, and devTools
are good to have on (you don’t have to write false in there as they default to true if you leave outgetDefaultMiddleware()
and devTools
.) Since they take time for every action that is dispatched, I turn it off so my programs can run faster. It’s good to have them on to check if you’re adhering to the Redux methodology though!
出于开发目的,最好使用immutableCheck
, serializableCheck
和devTools
(您不必在其中编写false,因为如果省略getDefaultMiddleware()
和devTools
,它们默认为true。)因为它们花费时间进行每个操作分派后,我将其关闭,这样我的程序可以运行得更快。 最好让他们检查您是否遵循Redux方法!
module.exports
all that and we’re ready to start using Redux Toolkit within the index.js of JeetBot.
module.exports
所有这些,我们准备开始在JeetBot的index.js中使用Redux Toolkit。
index.js (index.js)
// Discord Client
const Discord = require("discord.js");
const client = new Discord.Client({ partials: ["MESSAGE", "REACTION"] });
// Dotenv
require("dotenv").config();
// MongoDB Info
const database = require("./database/database");
const ServerInfo = require("./database/models/dbdiscordserverinfo");
// Handlers
const commandHandler = require("./commands");
// Redux store
const store = require("./redux/store");
const {
guildsSelector,
guildAdded,
guildRemoved,
} = require("./redux/guildsSlice");
// Utils
const { serverCache } = require("./utils/botUtils");
/**
* When the bot turns on, turn on the connection to DB
* Then Retrieve all Discord Servers and their specialized info
* Then store it within the redux store for later use.
*/
client.once("ready", async () => {
database
.then(() => console.log(`${client.user.tag} is connected to MongoDB.`))
.catch((err) => console.log(err));
try {
await Promise.all(
client.guilds.cache.map(async (guild) => {
let guildInfo = await ServerInfo.findOne({
server_id: guild.id,
});
if (!guildInfo) {
let serverInfo = new ServerInfo({
server_id: guild.id,
server_name: guild.name,
discord_owner_id: guild.ownerID,
});
try {
await serverInfo.save();
store.dispatch(guildAdded(serverCache(guildInfo)));
console.log(
`${guild.name} has been saved to the Database & to the Redux Store`
);
} catch {
(err) => console.log(err);
}
} else {
store.dispatch(guildAdded(serverCache(guildInfo)));
console.log(
`${guild.name} exists in the Database & has been added to Redux Store`
);
}
})
);
} catch {
(err) => console.log(err);
} finally {
console.log("*** This is the Store's status ***\n", store.getState());
console.log(`${client.user.tag} is Ready To Rock And Roll!`);
}
});
Here’s the fun part with Discord.js and Redux.js.
这是Discord.js和Redux.js的有趣部分。
Basically, this code is doing the following:
基本上,此代码执行以下操作:
- Jeetbot checks with Discord to see if Jeetbot has been added to new servers. Jeetbot与Discord检查以查看Jeetbot是否已添加到新服务器。
If it has, new guild information is added to the database and the local
store
.如果有,则将新的行会信息添加到数据库和本地
store
。If the bot sees that the server exists on the database already, it caches the information from MongoDB to the local
store
for later use.如果该僵尸程序发现该服务器已存在于数据库中,则它将信息从MongoDB缓存到本地
store
以备后用。
Simple right? But here’s the important line for our store at this point:
简单吧? 但是,这是我们商店目前的重要提示:
store.dispatch(guildAdded(serverCache(guildInfo)));
store.dispatch(guildAdded(serverCache(guildInfo)));
Note: My serverCache
function is returning an object that includes the Discord Server ID as _id
. It’s nothing special but the way I handled my database — it’s not the primary key there.
注意:我的 serverCache
函数返回的对象包括Discord Server ID作为 _id
。 除了我处理数据库的方式外,这没什么特别的-这不是那里的主键。
store
is the global state where we’re holding everything.store
是我们存放所有物品的全球状态。store.dispatch
is a method on the store that says “hey, we want to run an action.”store.dispatch
是商店中的一种方法,上面写着“嘿,我们要执行操作”。guildAdded
is a reducer I’ve created back in guildsSlice.js.guildAdded
是我在guildsSlice.js中创建的一个reducer。guildInfo
is the data I received and am passing through to the reducer.guildInfo
是我接收并传递给减速器的数据。
From there, for any command that occurs within our bot, we can check the store
using our guildsSelector
.
从那里,对于我们机器人中发生的任何命令,我们都可以使用guildsSelector
检查store
。
guildAdded
和guildRemoved
(Examples of guildAdded
and guildRemoved
)
// When Jeet joins a new server
client.on("guildCreate", async (guild) => {
let guildInfo = await ServerInfo.findOne({
server_id: guild.id,
});
if (!guildInfo) {
let serverInfo = new ServerInfo({
server_id: guild.id,
server_name: guild.name,
discord_owner_id: guild.ownerID,
});
try {
await serverInfo.save();
store.dispatch(guildAdded(serverCache(serverInfo)));
// The bot joined a new server, we saved to DB and the store.
} catch {
(err) => console.log(err);
}
} else {
console.log(`${guild.name} exists in the Database`);
}
});
Here’s another example of us adding a single Discord guild to the store:
这是我们向商店添加单个Discord公会的另一个示例:
store.dispatch(guildAdded(serverCache(serverInfo)));
// Jeet has been kicked off a Discord server
client.on("guildDelete", async (guild) => {
let guildInfo = await ServerInfo.findOne({
server_id: guild.id,
});
if (guildInfo) {
try {
store.dispatch(guildRemoved(serverCache(guildInfo)));
await guildInfo.deleteOne(); // example of using .deleteOne()
console.log(`${guild.name} has been deleted from the Database`);
} catch {
(err) => console.log(err);
}
} else {
console.log(`${guild.name} does not exist in the Database`);
}
});
Here is another example of the guild being removed:
这是公会被删除的另一个示例:
store.dispatch(guildRemoved(serverCache(guildInfo)));
That’s great — we’re now able to add to the Redux Store!
太好了-我们现在可以添加到Redux Store!
能够从商店中读取内容呢? (What About Being Able to Read From the Store?)
Here’s the first case where we’ll access data within our store instead of just adding/deleting data from it. In the Redux Toolkit docs, you’ll see that we have guildsSelector
created in guildsSlice.js. Refer here to see the selector methods that are available.
这是第一种情况,我们将访问商店中的数据,而不仅仅是从中添加/删除数据。 在Redux Toolkit文档中,您将看到我们在guildsSelector
创建了guildsSelector。 请参阅此处以查看可用的选择器方法。
// An event listener for new guild members
client.on("guildMemberAdd", async (member) => {
let clientGuildInfo = guildsSelector.selectById(
store.getState(),
member.guild.id
);
if (clientGuildInfo) {
if (clientGuildInfo.welcomeMessage.welcomeChannel) {
let channel = member.guild.channels.cache.find(
(ch) => ch.id === clientGuildInfo.welcomeMessage.welcomeChannel
);
if (!channel) return;
channel.send(clientGuildInfo.welcomeMessage.messageInfo);
} else {
return;
}
} else {
console.log(
`Jeet cannot find this server ${member.guild.id} in the client side storage.\n
Jeet will fetch this server and add it to the Redux Store.`
);
let guildInfo = await ServerInfo.findOne({
server_id: member.guild.id,
});
store.dispatch(guildAdded(serverCache(guildInfo)));
if (guildInfo.welcomeMessage.welcomeChannel) {
let channel = member.guild.channels.cache.find(
(ch) => ch.id === guildInfo.welcomeMessage.welcomeChannel
);
if (!channel) return;
channel.send(guildInfo.welcomeMessage.messageInfo);
} else {
return;
}
}
});
On line 3, we’re able to grab a deep clone of a Discord Guild within the store.
在第3行中,我们能够在商店中获取Discord公会的深层克隆。
guildsSelector.selectById(store.getState(), member.guild.id));
guildsSelector
— The import we did earlier from the guildsSlice.js. It was part of the createEntityAdapter which has a method on it called…
guildsSelector
—我们先前从guildsSlice.js执行的导入。 它是createEntityAdapter的一部分,其上有一个名为…的方法。
.selectById(state, entity.id)
— It takes two parameters. The current state and the Discord Server ID of the object we want to get.
.selectById(state, entity.id)
—它.selectById(state, entity.id)
两个参数。 我们要获取的对象的当前状态和Discord Server ID。
store.getState()
is how we retrieve the current state of the store.
store.getState()
是我们如何检索商店的当前状态。
member.guild.id
is a Discord.js object that finds the member’s message and the Discord Server ID where they’re messaging from.
member.guild.id
是Discord.js对象,用于查找成员的消息以及从中进行消息收发的Discord Server ID。
After I get my data for that specific Discord Server, I’m able to run my logic to see if there is a welcome message set by the admin or moderators of that Discord Server.
在获得该特定Discord Server的数据之后,我可以运行我的逻辑以查看该Discord Server的管理员或主持人是否设置了欢迎消息。
Great! Now I’m able to read from my store for the welcome messages to all the discord servers my bot is on. But what if a user wants to customize a message?
大! 现在,我可以从商店中读取到我的机器人所在的所有不和谐服务器的欢迎消息。 但是,如果用户想要自定义消息怎么办?
状态管理时间 (State Management Time)
Going ahead with the following code is the true beauty of Redux.js. Without being able to manage a global state, I would struggle to find a solution for all this.
继续执行以下代码是Redux.js的真正魅力。 在无法管理全球状态的情况下,我将很难为所有这些问题找到解决方案。
If you’re still with me, let's continue!
如果您仍然与我在一起,让我们继续吧!
// Command Handler
client.on("message", async (msg) => {
commandHandler(msg, store);
});
Here is the part where I had my original issues. In Vanilla JavaScript or at least within the current structure of my app using Discord.js, I was sending up an object where the store variable was before. Even if I did await commandHandler(msg, localStore)
it would not come back in time for the new data to appear locally. So this time around, I am sending up the async (msg)
and the store
.
这是我最初遇到的问题。 在Vanilla JavaScript中,或者至少在我使用Discord.js的应用程序的当前结构中,我正在发送一个存储变量之前的对象。 即使我确实await commandHandler(msg, localStore)
,也不会及时将新数据显示在本地。 所以这次,我要发送async (msg)
和store
。
commandHandler folder (index.js)
commandHandler文件夹(index.js)
// the index.js within commands folder
// lots of commands would be here but cut out for medium article
const edit = require('./moderation/edit');
const PREFIX = process.env.PREFIX;
const commands = {
'welcomeEdit': edit,
};
module.exports = async (msg, store) => {
const args = msg.content.split(/ +/);
if(args.length === 0 || !args[0].startsWith(PREFIX)) return;
const command = args.shift().substr(2);
if (Object.keys(commands).includes(command)) {
commands[command](msg, args, store)
}
}
The bot checks to see if there is a proper command being sent down to the bot. If it is, then continue to the specific command that was sent in this call.
机器人检查是否有适当的命令向下发送到机器人。 如果是,则继续执行此调用中发送的特定命令。
PREFIX = j.
andcommand = welcomeEdit
PREFIX = j.
和command = welcomeEdit
User types in j.welcomeEdit
and the flow will take the user to the edit command of the code.
用户在j.welcomeEdit
中j.welcomeEdit
,该流程会将用户带到代码的edit命令。
Note: it’s currently assigned as j.edit instead of j.welcomeEdit within the 1.1.2 version of Jeetbot.
注意:在1.1.2版本的Jeetbot中,它当前被分配为j.edit而不是j.welcomeEdit 。
edit.js (edit.js)
const Discord = require("discord.js");
const ServerInfo = require("../../database/models/dbdiscordserverinfo");
const { guildWelcomeMessageUpdated } = require("../../redux/guildsSlice");
const { serverCache } = require("../../utils/botUtils");
module.exports = async (msg, args, store) => {
if (msg.member.hasPermission(["MANAGE_MESSAGES"])) {
msg.channel.send(
`${msg.author}, what would you like your welcome message to be?\n(Don't forget to end your welcome message with j.end)`
);
let filter = (m) => !m.author.bot;
let collector = new Discord.MessageCollector(msg.channel, filter);
collector.on("collect", async (m, col) => {
if (msg.author.id === m.author.id && m.content.includes("j.end")) {
let welcomeMsg = m.content.slice(0, m.content.length - 5);
msg.channel.send(
`${msg.author}, where would you like the welcome message to go?`
);
let guildInfo = await ServerInfo.findOne({
server_id: msg.channel.guild.id,
});
guildInfo.welcomeMessage.messageInfo = welcomeMsg;
await guildInfo.save();
store.dispatch(guildWelcomeMessageUpdated(serverCache(guildInfo)));
}
if (msg.author.id === m.author.id && m.content.startsWith("<#")) {
let channel = m.content.slice(2, m.content.length - 1);
let guildInfo = await ServerInfo.findOne({
server_id: msg.channel.guild.id,
});
guildInfo.WelcomeMessage.WelcomeChannel = channel;
await guildInfo.save();
store.dispatch(guildWelcomeMessageUpdated(serverCache(guildInfo)));
msg.channel.send(`${msg.author}, Thanks I'll remember that!`);
collector.stop();
}
});
} else {
msg.channel.send(
`${msg.author.username} does not have the authority to edit the welcome message`
);
}
};
At this point, we’re a bit away from our index.js file and on top of that, a ton more messages are flying through to the commandHandler
file to see if they are legitimate commands or not. While we’re awaiting a message for the user to input, promises are being built on other servers that need to be resolved!
至此,我们离index.js文件还有点距离了,最重要的是,还有更多的消息正在飞到commandHandler
文件中,以查看它们是否是合法命令。 在等待用户输入消息时,promise正在其他需要解决的服务器上构建!
When I had previously entered a regular object and tried to return it, after the user finishes their prompt of entering a new welcome message, my local cache didn’t update.
当我以前输入常规对象并尝试返回它时,在用户完成输入新欢迎消息的提示后,我的本地缓存没有更新。
This time, I import the action guildWelcomeMessageUpdated
, we dispatch the action on the store, and we’ve basically come full circle with what we wanted to do.
这次,我导入动作guildWelcomeMessageUpdated
,我们在商店上分派了该动作,并且基本上已经完成了我们想做的事情。
store.dispatch(guildWelcomeMessageUpdated(serverCache(guildInfo)));
There are a lot of different things going on here but the long and short of it is that the program works as intended! By dispatching an action to my store, it updates the global state and I can use the new data the user had entered right away.
这里有很多不同的事情发生,但总的来说是程序可以按预期工作! 通过向我的商店发送操作,它会更新全局状态,并且我可以使用用户立即输入的新数据。
Whew!
ew!
If you’ve arrived at this point, I hope I was able to impart some knowledge or reaffirm something you’ve been thinking about.
如果您到了这一点,希望我能传授一些知识或重申您一直在考虑的事情。
I want to give many thanks to the teams that maintain Discord.js and Redux ToolKit. Also special thanks to the Reactiflux community on Discord, specifically phryneas#4779 and acemarke#9340 directing me towards the right direction when building the Redux store.
我要感谢维护Discord.js和Redux ToolKit的团队。 还要特别感谢Discord上的Reactiflux社区,特别是phryneas#4779和acemarke#9340在构建Redux商店时将我引向正确的方向。
It wasn’t long ago that I didn’t know how to code properly but thankfully with the help of Code Chrysalis and their immersive coding boot camp. I was able to find the confidence and ability to think critically about my code and swiftly seek solutions when there were problems.
不久之前,我还不知道如何正确编码,但幸好有了Code Chrysalis及其沉浸式编码训练营的帮助。 我找到了对代码进行认真思考的信心和能力,并在出现问题时Swift寻求解决方案。
When creating your own redux store for your applications, try to follow the Redux Style Guide. It’s a great way to format your Redux stores. Once it clicks, it’ll be like any tool in the box. Use it when you need it.
为应用程序创建自己的redux存储时,请尝试遵循Redux Style Guide 。 这是格式化Redux商店的好方法。 单击后,它将像框中的任何工具一样。 在需要时使用它。
I hope this was an educational post for those needing it.
我希望这对于那些需要它的人来说是一个教育性的职位。
If there is another solution out there, I would love to dive deep and tinker with it out some more. But through this experience, I felt like I leveled up a bit on my path to become a better programmer.
如果还有其他解决方案,我很乐意深入研究,并进一步完善它。 但是通过这种经验,我觉得自己在迈向成为更好的程序员的道路上有所提升。
tl; dr —我遇到了两个大问题 (tl;dr — I Had Two Big Issues)
- Too many reads/writes to the database. 对数据库的读取/写入过多。
- State mutation with asynchronousness not being returned properly. 具有异步性的状态突变未正确返回。
I solved them using the Redux Toolkit.
我使用Redux Toolkit解决了它们。
My bot caches local data and handles the global state properly.
我的机器人会缓存本地数据并正确处理全局状态。
翻译自: https://medium.com/better-programming/discord-bots-and-state-management-22775c1f7aeb