不和谐机器人 |使用 discord.js 和 MongoDB 进行排名(排行榜?)系统

Posted

技术标签:

【中文标题】不和谐机器人 |使用 discord.js 和 MongoDB 进行排名(排行榜?)系统【英文标题】:Discord Bot | Ranking (Leaderboard?) System with discord.js and MongoDB 【发布时间】:2021-05-30 06:07:32 【问题描述】:

关于使用 MongoDB 在 discord.js 中的排名(排行榜...?)系统,我有几个问题要问。我正在关注 tutorial made by CodeLyon 来设置 MongoDB,但之后我继续创建了自己的 XP 系统。

我知道我可以对配置文件模型进行排序,但我不知道如何。另外,我如何给每个配置文件模型一个等级?例如,如果一个名为 Dummy 的用户在列表中排名第 5,我如何给他排名 5? 请同时阅读代码后面的部分。

以下是我认为重要的文件:

profileSchema.js:创建配置文件模型

const mongoose = require('mongoose');

const profileSchema = new mongoose.Schema(
    userID:  type: String, require: true, unique: true,
    serverID:  type: String, require: true,
    coins:  type: Number, default: 1000,
    bank:  type: Number ,
    username: type: String, require: true ,
    discriminator:  type: String, require: true ,
    xp:  type: Number, default: 1 ,
    xpShow:  type: Number ,
    level:  type: Number, default: 1,
    xpReq:  type: Number
)

const model = mongoose.model('ProfileModels', profileSchema);

module.exports = model;

message.js:消息事件

require('dotenv').config();
const profileModel = require("../../models/profileSchema");
//create cooldowns map
const cooldowns = new Map();

module.exports = async (Discord, client, message) => 
    const prefix = process.env.PREFIX;
    if (!message.content.startsWith(prefix) || message.author.bot) return;

    let profileData;
    try
        profileData = await profileModel.findOne( userID: message.author.id );
        if(!profileData)
            let profile = await profileModel.create(
                userID: message.author.id,
                serverID: message.guild.id,
                coins: 1000,
                bank: 0,
                username: message.author.username,
                discriminator: message.author.discriminator,
                xp: 1,
                xpShow: 1,
                level: 1,
                xpReq: 100,
            );
        
    catch(err)
        console.log(err);
    

    
    
    try
        const levex = Math.floor(((Math.sqrt(2500 +  (200 * profileData.xp))) - 50) / 100) + 1;
        const reqXP = levex * 100;
        const roundXP = 50 * (levex * (levex - 1));
        const showXP = profileData.xp - roundXP;

        const response3 = await profileModel.findOneAndUpdate(
            userID: message.author.id,
        ,
        
            level: levex,
            xpShow: showXP,
            xpReq: reqXP,
        );

     catch(err)
        console.log("------------------------------------------");
        console.log("   ERROR (Probably an invalid variable)");
        console.log("------------------------------------------");
        console.log(err);
    

    const args = message.content.slice(prefix.length).split(/ +/)
    const cmd = args.shift().toLowerCase();

    const command = client.commands.get(cmd) || client.commands.find((a) => a.aliases && a.aliases.includes(cmd));

    const validPermissions = [
        "CREATE_INSTANT_INVITE",
        "KICK_MEMBERS",
        "BAN_MEMBERS",
        "ADMINISTRATOR",
        "MANAGE_CHANNELS",
        "MANAGE_GUILD",
        "ADD_REACTIONS",
        "VIEW_AUDIT_LOG",
        "PRIORITY_SPEAKER",
        "STREAM",
        "VIEW_CHANNEL",
        "SEND_MESSAGES",
        "SEND_TTS_MESSAGES",
        "MANAGE_MESSAGES",
        "EMBED_LINKS",
        "ATTACH_FILES",
        "READ_MESSAGE_HISTORY",
        "MENTION_EVERYONE",
        "USE_EXTERNAL_EMOJIS",
        "VIEW_GUILD_INSIGHTS",
        "CONNECT",
        "SPEAK",
        "MUTE_MEMBERS",
        "DEAFEN_MEMBERS",
        "MOVE_MEMBERS",
        "USE_VAD",
        "CHANGE_NICKNAME",
        "MANAGE_NICKNAMES",
        "MANAGE_ROLES",
        "MANAGE_WEBHOOKS",
        "MANAGE_EMOJIS",
    ]
        
        
    try
        if(command.permissions.length)
            let invalidPerms = []
            for(const perm of command.permissions)
                if(!validPermissions.includes(perm))
                    return console.log(`Invalid Permissions $perm`);
                
                if(!message.member.hasPermission(perm))
                invalidPerms.push(perm);
                
            
                if (invalidPerms.length)
            return message.channel.send(`Missing Permissions: \`$invalidPerms\``);
            
        
     catch (err)
        console.log("------------------------------------------");
        console.log("   ERROR (Probably an invalid command)");
        console.log("------------------------------------------");
        console.log(err);
    

// If cooldowns map doesn't have a command.name key then create one.
    try
        if(!cooldowns.has(command.name))
            cooldowns.set(command.name, new Discord.Collection());
        
     catch (err) 
        return message.reply("my commands are limited. I can\'t find that command!");
    

    const current_time = Date.now();
    const time_stamps = cooldowns.get(command.name);
    const cooldown_amount = (command.cooldown) * 1000;

//If time_stamps has a key with the author's id then check the expiration time to send a message to a user.
    if(time_stamps.has(message.author.id))
        const expiration_time = time_stamps.get(message.author.id) + cooldown_amount;

        if(current_time < expiration_time)
            const time_left = (expiration_time - current_time) / 1000;

            return message.reply(`Please wait $time_left.toFixed(1) more seconds before using $command.name`);
        
    

//If the author's id is not in time_stamps then add them with the current time.
    time_stamps.set(message.author.id, current_time);
//Delete the user's id once the cooldown is over.
    setTimeout(() => time_stamps.delete(message.author.id), cooldown_amount);

    try
        //execute command
        command.execute(client, message, cmd, args, Discord, profileData);
     catch (err)
        message.reply("There was an error trying to execute this command!");
        console.log(err);
    

我想在 profileSchema 和 profileData 中创建一个“rank”配置文件变量,然后我想将用户的排名设置为这个配置文件变量。

如果需要额外说明,请在下面的 cmets 中告诉我。

【问题讨论】:

【参考方案1】:

profileSchema 中添加“等级”属性将是一种低效的方法。因为排名会根据其他人的 XP 变化而变化,因此一旦 一个 用户更新/增加他们的 XP,您就必须更新集合中的每个文档。实现相同结果的更好方法是编写一个函数,该函数将接受用户 ID 作为其第一个参数,对整个集合进行排序(升序),找到用户文档的索引,并将其递增 1(因为索引从0).

执行该任务的函数如下所示:

            //A file on it's own, which will export the function to make it re-usable in other files.
            const mongoose = require("mongoose");
            //modify "@models" to locate profileSchema.js
            const profileModel = require("@models/profileSchema");

            /**
             * 
             * @param ID The Discord ID of the user you want to locate their Rank in the collection
             * @returns Number The rank of the user
             */
            module.exports = async (ID) => 
                //https://docs.mongodb.com/manual/reference/method/cursor.sort/
                const userDoc = await profileModel.findOne( userID: ID ).catch(e => console.log(e));
                //We return -1 (false) if the user doesn't have a document
                if(!userDoc) return -1;
                //sortedCollection is an array.
                const sortedCollection = await profileModel.find().sort( xp: -1 ).catch(e => console.log(e));
                //https://developer.mozilla.org/en-US/docs/Web/javascript/Reference/Global_Objects/Array/indexOf
                //indexOf returns the index, we increment it by 1 to get the Rank of the user
                return sortedCollection.indexOf(userDoc) + 1;
            

在上面的示例中,我假设您希望将函数放在自己的文件中以避免更长的index.js。该函数是可导入的,这意味着您可以在根目录的任何地方使用它,如果您想在index.js 中使用它,只需定义一个引用该文件的变量:

     const getRank = require("location to the file");

然后你可以通过调用getRank(message.author.id)index.js中使用它。

注意 提供的示例不尊重大型 mongodb 集合。 .find() 方法返回该集合中所有 文档的数组,这会消耗大量 RAM,这可能会导致客户端响应延迟。为了解决这个问题,您可以使用像 Redis 这样的辅助(本地)数据库,这个答案变得很长,所以我将留下 this youtube 链接,它解释了 Redis 是什么以及为什么使用它来检索数据是有效的通常需要很长时间才能获得。

【讨论】:

嗨莫希!感谢您回答这个问题,即使它是在 5 个月前发布的!那时,我真的很讨厌 Javascript 并且知道的不多。那时,我想使用与您类似的方法,但我不知道如何对文档进行排序。多亏了你,我现在了解排序了! +1 并标记为正确。 很高兴知道你进步了,继续努力!

以上是关于不和谐机器人 |使用 discord.js 和 MongoDB 进行排名(排行榜?)系统的主要内容,如果未能解决你的问题,请参考以下文章

Node.Js 版本 14 抛出 e;不和谐机器人的错误(discord.js)

剪切 Discord 消息的特定部分?不和谐.js

Discord.js 在嵌入链接中将 api 连接到不和谐机器人

故障上托管的不和谐机器人错误:找不到模块'discord.js'

Discord.js JavaScript 不和谐客户端无缘无故断开和重新连接

我的不和谐机器人上的数据库(Discord.js + mysql)