Discord.js level system not leveling up as XP keeps going - mongodb

Coding a discord.js bot using WOK's tutorials, made a basic leveling system, I will add more features to it in the future, and the code is working mostly, except that the level doesn't update when the XP reaches the right value. Every message takes up 10 XP and the amount of XP needed to level up is (level number * 100) e.g. level 1->2 = 100, level 2->3 = 200 etc.
The code works and doesn't return any errors until when I type a certain amount of messages that should get me to the next level, i get this error: drainWaitQueue(topology[kWaitQueue], new MongoError('Topology is closed, please connect'));
And the level-up message from the bot doesn't send and the mongo database just keeps adding XP instead of resetting it to 0 and adding 1 level.
Here is my code:
const mongo = require('./mongo')
const profileSchema = require('./schemas/profile-schema')
const Discord = require('discord.js')
module.exports = (client) => {
client.on('message', (message) => {
const { guild, member } = message
addXP(guild.id, member.id, 10, message)
})
}
const getNeededXP = level => level * 100
const addXP = async (guildId, userId, xpToAdd, message) => {
await mongo().then(async (mongoose) => {
try {
const result = await profileSchema.findOneAndUpdate({
guildId,
userId,
}, {
guildId,
userId,
$inc: {
xp: xpToAdd,
},
}, {
upsert: true,
new: true,
})
let { xp, level } = result
const needed = getNeededXP(level)
if (xp >= needed) {
++level
xp -= needed
const levelUpEmbed = new Discord.MessageEmbed()
.setTitle('**NEW LEVEL REACHED**')
.setDescription(`You are now level ${level} with ${xp} experience! You now need ${getNeededXP(level)} XP to reach the next level!`)
message.channel.send()
await profileSchema.updateOne({
guildId,
userId
}, {
level,
xp
})
}
} finally {
mongoose.connection.close()
}
})
}
module.exports.addXP = addXP
Here is the schema for the mongo database, which also includes a coins amount for an economy system (the economy system works perfectly fine):
const mongoose = require('mongoose')
const reqString = {
type: String,
required: true
}
const profileSchema = mongoose.Schema({
guildId: reqString,
userId: reqString,
coins: {
type: Number,
default: 0,
},
xp: {
type: Number,
default: 0
},
level: {
type: Number,
default: 1,
}
})
module.exports = mongoose.model('profiles', profileSchema)
Please advise.

Related

Bot doesnt want to take id from db

I'm making a command where u can setup a bot status channel. Everything so far worked fine, db even writes down id of guild and channel but when I want to use it in ready.js is says its not defined.
Little help would be handy and grateful
Code:
https://pastebin.com/GnxtxFwG - main command
https://pastebin.com/hhy90czw - schema
https://pastebin.com/jVDGu43T - error
Error file:
const { EmbedBuilder } = require('discord.js');
const { channelId } = require('../../schemas/status');
module.exports = {
name: "ready",
once: "true",
async execute(client) {
console.log(channelId)
console.log('Bot is up and ready to work!')
const uptime = new EmbedBuilder()
.setColor('ffd9c0')
.addFields(
{ name: 'Capy is back and online!', value: 'Bot was probably offline due to a bug or maintenance.'},
{ name: '\u200b', value: 'If Capy isnt working how is he supposed to be contact on of our Developers or contact <#574849327650963469> directly.'},
)
const channel = await client.channels.cache.get(channelId);
channel.send({ embeds: [uptime] });
setInterval(client.pickPresence, 10 * 1000);
},
};
Client on function
const { EmbedBuilder } = require('discord.js');
const { Statuses } = require('../../schemas/status');
const uptime = require('../../commands/tools/status')
module.exports = {
name: "ready",
once: "true",
async execute(client) {
console.log(Statuses.channelId)
console.log('Bot is up and ready to work!')
const uptimeEmbed = new EmbedBuilder()
.setColor('ffd9c0')
.addFields(
{ name: 'Capy is back and online!', value: 'Bot was probably offline due to a bug or maintenance.'},
{ name: '\u200b', value: 'If Capy isnt working how is he supposed to be contact on of our Developers or contact <#574849327650963469> directly.'},
)
const channel = await client.channels.cache.get(Statuses.channelId);
Statuses.findById({ channelId: message.guild.id }, async (err, data) => {
if (data) {
channel.send({ embeds: [uptimeEmbed] });
} else {
new Statuses ({
guildId: interaction.guild.id,
chnanelId: uptime ? uptime : "None"
}).save()
console.log("Status channel wasnt set on this server yet!")
}
});
setInterval(client.pickPresence, 10 * 1000);
},
};
Schema
const { Schema, model } = require('mongoose');
const statusSchema = new Schema({
guildId: String,
channelId: String,
});
module.exports = model("Status", statusSchema, "Statuses");
Main command
const {
SlashCommandBuilder,
ChatInputCommandInteraction,
EmbedBuilder,
} = require("discord.js");
const Statuses = require('../../schemas/status');
module.exports = {
data: new SlashCommandBuilder()
.setName("status")
.setDescription("Select the language of the bot")
.addChannelOption(uptime => {
return uptime
.setName("channel")
.setDescription("Channel you want to send the FAQ embed in")
.setRequired(true)
}),
/**
*
* #param {ChatInputCommandInteraction} interaction
* #param {Client} client
*/
async execute(interaction, client) {
const { options } = interaction;
const uptime = options.getChannel("channel");
const user = interaction.user;
const status = new EmbedBuilder()
.setColor("#bb6464")
.setDescription(`Status message was set to ${uptime}!`)
.setFooter({ text: `Request from ${interaction.user.username}`, iconURL: user.displayAvatarURL()})
.setTimestamp();
const status2 = new EmbedBuilder()
.setColor("#bb6464")
.setDescription(`Status message was changed to ${uptime}!`)
.setFooter({ text: `Request from ${interaction.user.username}`, iconURL: user.displayAvatarURL()})
.setTimestamp();
let statusProfile = await Statuses.findOne({ channelId: interaction.guild.id });
if (!statusProfile) {
statusProfile = await new Statuses({
guildId: interaction.guild.id,
channelId: uptime,
});
await statusProfile.save().catch(console.error);
interaction.reply({ embeds: [status] })
console.log(statusProfile);
} else {
await interaction.reply({ embeds: [status2] });
console.log(statusProfile);
}
}
}

Using the $inc function across the MongoDB documents

I am currently working on a moderation system for my Discord bot and came across an unexpected issue. I've been using the $inc function to increase the values for a single document, though I have sadly not achieved to use the $inc function across multiple different documents, meaning I would like to increase ($inc) the value of the new document according to the numbers of the previous document.
Example: Cases
Current code:
async run(client, message, args, Discord) {
const targetMention = message.mentions.users.first()
const userid = args[0]
const targetId = client.users.cache.find(user => user.id === userid)
const username = targetMention.tag
if(targetMention){
args.shift()
const userId = targetMention.id
const WarnedBy = message.author.tag
const reason = args.join(' ')
if(!reason) {
message.delete()
message.reply('You must state the reason behind the warning you are attempting to apply.').then(message => {
message.delete({ timeout: 6000})
});
return;
}
const warningApplied = new Discord.MessageEmbed()
.setColor('#ffd200')
.setDescription(`A warning has been applied to ${targetMention.tag} :shield:`)
let reply = await message.reply(warningApplied)
let replyID = reply.id
message.reply(replyID)
const warning = {
UserId: userId,
WarnedBy: WarnedBy,
Timestamp: new Date().getTime(),
Reason: reason,
}
await database().then(async database => {
try{
await warnsSchema.findOneAndUpdate({
Username: username,
MessageID: replyID
}, {
$inc: {
Case: 1
},
WarnedBy: WarnedBy,
$push: {
warning: warning
}
}, {
upsert: true
})
} finally {
database.connection.close()
}
})
}
if(targetId){
args.shift()
const userId = message.member.id
const WarnedBy = message.author.tag
const reason = args.join(' ')
if(!reason) {
message.delete()
message.reply('You must state the reason behind the warning you are attempting to apply.').then(message => {
message.delete({ timeout: 6000})
});
return;
}
const warning = {
userId: userId,
WarnedBy: WarnedBy,
timestamp: new Date().getTime(),
reason: reason
}
await database().then(async database => {
try{
await warnsSchema.findOneAndUpdate({
userId,
}, {
$inc: {
Case: 1
},
WarnedBy: WarnedBy,
$push: {
warning: warning
}
}, {
upsert: true
})
} finally {
database.connection.close()
}
const warningApplied = new Discord.MessageEmbed()
.setColor('#ffd200')
.setDescription(`A warning has been applied to ${targetId.tag} :shield:`)
message.reply(warningApplied)
message.delete();
})
}
}
Schema attached to the Code:
const warnsSchema = database.Schema({
Username: {
type: String,
required: true
},
MessageID: {
type: String,
required: true
},
Case: {
type: Number,
required: true
},
warning: {
type: [Object],
required: true
}
})
module.exports = database.model('punishments', warnsSchema)
Answer to my own question. For all of those who are attempting to do exactly the same as me, there is an easier way to get this to properly work. The $inc (increase) function will not work as the main property of a document. An easier way to implement this into your database would be by creating a .json file within your Discord bot files and adding a line such as the following:
{
"Number": 0
}
After that, you'd want to "npm i fs" in order to read directories in live time.
You can proceed to add a function to either increase or decrease the value of the "Number".
You must make sure to import the variable to your current coding document by typing:
const {Number} = require('./config.json')
config.json can be named in any way, it just serves as an example.
Now you'd be able to console.log(Number) in order to make sure the number is what you expected it to be, as well as you can now increase it by typing Number+=[amount]
Hope it was helpful.

How to make a, "setWelcome" command using database(MongoDB) and Discord.js?

I want to make a command for my bot that'll enable people to make a custom welcome message for their guild. So far, I'm able to get the information into the database, but I don't know how to tell my bot to fetch the data and send the message in a specific channel for a specific guild.
This is the code for when a member joins the guild
client.on('guildMemberAdd, message', async (member, message) => {
if (member.bot) {
return
} else {
const welcome = require('./models/welcSchema');
const welcSchema = await welcome.findOne({
GuildID: message.guild.id
})
if (welcSchema) {
const mssg = welcSchema.Message;
const chan = welcSchema.ChannelID;
member.guild.channels.resolve(chan).send(mssg);
}
}
});
and here's the welcSchema.
const mongoose = require('mongoose');
const welcSchema = new mongoose.Schema({
GuildID: {
type: String,
required: true
},
Message: {
type: String,
required: true
},
ChannelID: {
type: String,
required: true
}
});
module.exports = mongoose.model(`welcomes`, welcSchema);
I don't get any errors, the bot just doesn't send anything when a member joins.
Nevermind I figured it out, I removed the message thing at the top of the code and where it says, "message.guild.id" and changed it to, "member.guild.id".

MongoDB doesn't add a new user after previous request with UNIQUE referral code. MongoDB webpage "REFRESH" helps only. What I did wrong?

Auth.js (CONTROLLER):
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const User = require('../models/User')
const keys = require('../config/keys')
const errorHandler = require('../utils/errorHandler')
// REGISTRATION
module.exports.register = async function(req, res) {
const candidate = await User.findOne({email: req.body.email})
const phone = await User.findOne({phone: req.body.phone})
if (candidate) {
// user exist
res.status(409).json({
message: 'Email is taken. Try another one!'
})
} else {
// new user
const salt = bcrypt.genSaltSync(10)
const password = req.body.password
const user = new User({
username: req.body.username,
password: bcrypt.hashSync(password, salt),
})
try {
await user.save()
res.status(201).json(user)
} catch(e) {
errorHandler(res, e)
}
}
}
Auth.js (ROUTES):
const express = require('express')
const controller = require('../controllers/auth')
const router = express.Router()
//localhost:5000/api/auth/register
router.post('/register', controller.register)
module.exports = router
User.js(MODEL):
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const userSchema = new Schema({
username: {
type: String,
unique: false
},
password: {
type: String,
required: true
},
codeRef: {
type: String,
default: makeid(6).toString(),
unique: true
}
})
// GENERATE RANDOM REFFERAL LINK
function makeid(length) {
var result = ''
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var charactersLength = characters.length
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
module.exports = mongoose.model('users', userSchema)
Everything works fine, the new user added successfully.
The problem comes with another added user after the first one. Generated referral code gives error until we refresh the MongoDB database or restart the server (NPM).
(Results from POSTMAN)
{
"success": false,
"message": "E11000 duplicate key error collection: Cluster.users index: codeRef_1 dup key: { codeRef: \"DDP1SF\" }"
}
Logs from console:
POST /api/auth/register 201 457.563 ms - 350
POST /api/auth/register 500 304.148 ms - 142
POST /api/auth/register 500 190.155 ms - 142
Tried to change the const to var... Should I clear the Schema data of previous request somehow? Because my generated random code for 6 symbols is the same until refresh.
MODELS should NOT contain functions because they are static.
Placing the function inside CONTROLLER file did the work.
// GENERATE RANDOM REFFERAL LINK
function makeid(length) {
var result = ''
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
var charactersLength = characters.length
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
To show the response after the server was updated, use new: true :
const user = new User({
username: req.body.username,
password: bcrypt.hashSync(password, salt),
codeRef: makeid(6).toString(),
new: true
})

How can I increment a counter variable in LoopBack 4 with a MongoDB datasource?

I'm trying to convert my Nodejs Express app to Loopback 4 and I can't figure out how to increment a counter. In my Angular 9 app when a user clicks an icon a counter is incremented. This works perfectly in Express
In Express
const updateIconCount = async function (dataset, collection = 'icons') {
let query = { _id: new ObjectId(dataset.id), userId: dataset.userId };
return await mongoController.update(
collection,
query,
{ $inc: { counter: 1 } },
function (err, res) {
logAccess(res, 'debug', true, 'function update updateIconLink');
if (err) {
return false;
} else {
return true;
}
}
);
};
I tried to first get the value of counter and then increment but every time I save VS Code reformats the code in an an unusual way. In this snippet I commented out the line of code that causes this reformatting. I can set the counter value, e.g. 100.
In Loopback 4
#patch('/icons/count/{id}', {
responses: {
'204': {
description: 'Icons PATCH success',
},
},
})
async incrementCountById(
#param.path.string('id') id: string,
#requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Icons, {partial: true}),
},
},
})
icons: Icons,
): Promise<void> {
// let targetIcon = this.findById(id).then(icon => {return icon});
icons.counter = 100;
console.log(icons.counter);
await this.iconsRepository.updateById(id, icons);
}
How do I implement { $inc: { counter: 1 } } in Loopback 4?
Added to aid solution
My mongo.datasource.ts
import {inject, lifeCycleObserver, LifeCycleObserver} from '#loopback/core';
import {juggler} from '#loopback/repository';
const config = {
name: 'mongo',
connector: 'mongodb',
url: '',
host: '192.168.253.53',
port: 32813,
user: '',
password: '',
database: 'firstgame',
useNewUrlParser: true,
allowExtendedOperators: true,
};
// Observe application's life cycle to disconnect the datasource when
// application is stopped. This allows the application to be shut down
// gracefully. The `stop()` method is inherited from `juggler.DataSource`.
// Learn more at https://loopback.io/doc/en/lb4/Life-cycle.html
#lifeCycleObserver('datasource')
export class MongoDataSource extends juggler.DataSource
implements LifeCycleObserver {
static dataSourceName = 'mongo';
static readonly defaultConfig = config;
constructor(
#inject('datasources.config.mongo', {optional: true})
dsConfig: object = config,
) {
super(dsConfig);
}
}
Amended endpoint
#patch('/icons/count/{id}', {
responses: {
'204': {
description: 'Icons PATCH success',
},
},
})
async incrementCountById(
#param.path.string('id') id: string,
#requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Icons, {partial: true}),
},
},
})
icons: Icons,
): Promise<void> {
console.log(id);
// #ts-ignore
await this.iconsRepository.updateById(id, {$inc: {counter: 1}});//this line fails
// icons.counter = 101; //these lines will set the icon counter to 101 so I know it is connecting to the mongodb
// await this.iconsRepository.updateById(id, icons);
}
You can use the mongo update-operators.
Basically, you just have to set allowExtendedOperators=true at your MongoDB datasource definition (guide). After that, you can directly use these operators.
Usage example:
// increment icon.counter by 3
await this.iconsRepository.updateById(id, {$inc: {counter: 3}} as Partial<Counter>);
Currently, these operators are missing from the lb4 types so you must cheat typescript to accept them. It's ugly but that's the only solution I could find right now.
You can follow this issue to see what's going on with these operators.