I'm coding a Discord bot in java script right now. I use MongoDB to store a "heat" value for every user in my server. (The "heat-system" adds a certain amount to a percentage whenever the user does something wrong and takes it off again after some time, to be more specific: -5 percent every minute in my example).
To do that, I want to use Model.updateMany() in a loop. As you see below, I used the filter parameter to find every document related to my server. The question now is how I can take these 5% off the stored value because it's not static, but dynamic.
const { Client } = require('discord.js');
const mongoose = require('mongoose');
//using modules and event handlers
module.exports = {
name: 'ready',
/**
* #param {Client} client
*/
async execute(client) {
const heatdb = require('/app/models/heatDB.js');
//how often the loop should pe repeated
const interval = 10 * 60 * 1000;
console.log('Client ready.')
//connnecting my data
mongoose.connect(
'I put the mongoose link here', {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => {
console.log('Data connected.')
}).catch((err) => {
console.log(err)
});
//loop begins here
setInterval(function(){
//filtered so that it's only searching for documents having the guild ID in it
heatdb.updateMany({ GuildID: "000000000000000000"}, {Heat: parseInt(/*needed value*/) - 5}})
}, interval);
},
};
And also see how my model is built below:
const { Schema, model } = require('mongoose');
module.exports = model("heatDB", new Schema({
GuildID: String,
UserID: String,
Heat: String,
}))
If you need anything else to help me, please let me know.
Thank you in advance,
Hydra
If your Heat value is a Number in Schema instead of String then you can try this-
heatdb.updateMany({ GuildID: "000000000000000000"},{$mul:{"Heat":0.95}})
Explanation:- you want to reduce Heat every time 5% percent then you can use mul
operator & set your heat value to 95% of current value. It will give you 5% deduction every time.
Related
I made an adventure command that searches for a persons profile in a Mongo database, then uses that info to calculate xp, coins, etc. I have made several other commands that use the database that work perfectly. This one, however, starts out fine by finding the correct document, but then when I try to populate the command with info from the called up document, it only pulls info from the first document in the database which belongs to my profile. So, everytime someone does adventure, it calculates everything based on my profile, then goes and adds it to the correct one. So they keep earning xp and coins but not according to their own profile info. See code below.
const { SlashCommandBuilder } = require('#discordjs/builders');
const { Collection, MessageActionRow, MessageButton } = require("discord.js");
const econModel = require('../models/econModel');
const humanizeDuration = require('humanize-duration');
const adventureCooldowns = new Collection();
module.exports = {
data: new SlashCommandBuilder()
.setName("adventure")
.setDescription("Explore the world and find treasure"),
execute(interaction){
const Member = interaction.member;
const memberHasAdvCooldown = adventureCooldowns.has(Member.id);
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
if(memberHasAdvCooldown){
const remaining1 = adventureCooldowns.get(Member.id) - new Date();
const remainingFormatted1 = humanizeDuration(remaining1, {
language: 'en',
round: true,
units: ['h', 'm', 's'],
})
interaction.reply({ content: `You must wait ${remainingFormatted1} for the cooldown period to end before adventuring again.`, ephemeral: true});
}
if(!adventureCooldowns.has(Member.id)){
econModel.findOne({ MemberId: Member.id }, async (err, Account) => {
if(err) throw err;
if(Account) {
console.log(`ID: ${Member.id}\n${Member.nickname}`);
const curXp = Account.Exp;
console.log(`Current XP: ${curXp}`);
const curLvl = Account.Level;
console.log(`Curent Level: ${curLvl}`);
const coinBonus = Account.coinBonus;
console.log(`Coin Bonus: ${coinBonus}`);
const xpBonus = Account.expBonus;
console.log(`XP Bonus: ${xpBonus}`);
const xpBase = Math.floor(Math.random() * 10 + 50);
console.log(`Base XP: ${xpBase}`);
const xpLvlBonus = xpBase * curLvl;
console.log(`lvl XP: ${xpLvlBonus}`);
const xpTotal = Math.floor(xpLvlBonus * xpBonus);
console.log(`Total: ${xpTotal}`);
const Coins = getRandomInt(25, 200);
console.log(`Base coins: ${Coins}`);
const coinAdd = Math.floor(Coins * coinBonus);
console.log(`total coins: ${coinAdd}`);
const nextLvl = (500 * Math.pow(2, curLvl))/2;
console.log(`Next level: ${nextLvl}`);
const newExp = curXp + xpTotal;
if(nextLvl <= newExp){
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Level: +1} });
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Fowlercoins: +coinAdd} });
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Exp: +xpTotal} });
interaction.reply({ content: `${Member.nickname}\nYou have leveled up! You are now level ${Account.Level + 1}!\nYou gained ${xpTotal} experience and found ${coinAdd} coins.`, components: [] });
econModel.collection.updateOne({ MemberID: Member.id }, { $set: {Next: nextLvl} });
adventureCooldowns.set(Member.id, Date.now() + 5000); //5000 is 5 seconds
setTimeout(() => adventureCooldowns.delete(Member.id), 5000);
} else{
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Fowlercoins: +coinAdd} });
econModel.collection.updateOne({ MemberID: Member.id }, { $inc: {Exp: +xpTotal} });
interaction.reply({ content: `${Member.nickname}\nYou gained ${xpTotal} experience and found ${coinAdd} coins.`, components: [] })
econModel.collection.updateOne({ MemberID: Member.id }, { $set: {Next: nextLvl} });
adventureCooldowns.set(Member.id, Date.now() + 5000); //5000 is 5 seconds
setTimeout(() => adventureCooldowns.delete(Member.id), 5000);
}
} else{
interaction.reply({ embeds: [new MessageEmbed()
.setTitle("ECONOMY SYSTEM")
.setColor("#3be820")
.setDescription(`${interaction.member.nickname} does not have an account! To create one, use /economy start!`)
.setFooter({ text: 'Rum Bot 1.4.5' })]});
}
})
}
}
}
As you can see, the first thing it tries to use is Account.Exp from the called upon document, but it always fills it in with the first document in the database. Any idea how to stop this from happening? I have been trying for a week and have yet to make any progress.
The additional information from the tests in the comment is very helpful and informative.
It sounds like the actual data in your system is not as you expect it to be. So the system is probably behaving correctly, and the unexpected behavior is a consequence of the current data. Walking through parts of the comment in order will help this answer.
I used the .count method as well as the .findOne() method. The count brought back 10 documents, despite telling it to find the one with a specific ID
The phrasing of "despite telling it to find the one [document] with a specific ID" here is interesting. Is there a unique index in the econModel collection on the MemberId field? If not, then there is no constraint in the database itself to enforce the implied claim that there should only be one document with a given value for the MemberId field.
The findOne query acted differently, though. Despite asking for a specific document (one that wasn't the first one in the collection) it would still return with the first document. Seems like the findOne query isn't operating correctly.
Why are you assuming that the output of findOne({MemberId: <val>}) is wrong? Did the document that was returned have a different value for its MemberId field than the one you queried with?
Based on your testing, I am under the impression that there are 10 documents in your econModel collection that have a value for their MemberId field that matches the one you are searching against. As next steps I would recommend examining all of them (.find({MemberId: <val>})), updating the values if they are not as expected, and creating a unique index on the field if you want the database to enforce uniqueness on that field going forward. May also need to also double check that there is not some bug in the code (as opposed to manual testing) which caused the discrepancy in the first place.
I am trying to use a value (Discord User ID stored as a string) stored via quick.db in my code, but it returns me the error user_id: Value "[object Promise]" is not snowflake. I've spent hours trying to figure it out but it just doesn't work. If I store the ID directly in the code it works just fine and I can fetch the user.
This is my "ready.js" file. The cron package lets me define at what time of the day that part of code is being executed. I don't think it's a part of the problem.
const Discord = require("discord.js")
const cron = require('cron');
const path = require('path');
const { QuickDB } = require("quick.db");
const db = new QuickDB()
module.exports = client => {
console.log(`${client.user.username} ist online`)
client.user.setActivity('Online!', { type: 'PLAYING' });
let userid1 = db.get("id1.string");
let scheduledMessage = new cron.CronJob('00 00 08 * * *', () => {
client.users.fetch(userid1).then(user => {
user.send('Test').catch(err => {
let channel = client.channels.cache.get('998568073034465341')
channel.send(`${user} blocked the bot`)
})
})
})
scheduledMessage.start()
}
This is where I want to utilize a User ID stored via quick.db in "id1.string"
client.users.fetch(userid1).then(user => {
-> This doesn't work
client.users.fetch(400120540989227010).then(user => {
-> This is working fine
I've already tried using
`${userid1}`
but this also doesn't work
I'd be so happy if someone could help me with that.
db.get("id1.string") is an async function, meaning unless you put await behind it, it will returns a Promise who isn't finished yet. It's like wanting to send a discord message. You can't just get the message immediatly since because of your connection and the api's connection. It takes time. So to bypass this you have to use the await keyword before the .get method (and async the main function here) so that it won't execute the rest of the code until you get what you want from the database.
let userid1 = db.get("id1.string"); // What you have now
let userid1 = await db.get("id1.string"); // What you should do instead
module.exports = client => { // What you have now
module.exports = async client => { // What you should do instead
I have a screen on my app where a user inputs a number {x} and from this number I would like to create a collection in the programs doc and then add {x} documents to the collection.
Only one document gets added to the collection.
const handleContinue = async () => {
const batch = writeBatch(db);
const blockArray = [...Array(blockCount).keys()];
// use the program name as the ID.
const docRef = doc(db, `Users/${userStore.uid}/programs/${programName}`);
const payload = {
title: programName,
units: programUnits,
system: programSystem,
status: programStatus,
days: dayCount,
blocks: blockCount,
};
await setDoc(docRef, payload, { merge: true });
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const dRef = doc(db, `Users/${userStore.uid}/programs/${programName}`);
const cRef = collection(dRef, "blocks");
blockArray.forEach((index) => {
const insert = doc(cRef, `block_${index}`);
batch.set(insert, { name: `Block ${index}` });
});
await batch.commit();
}
};
Structure I'm expecting starting from programs doc
-programs (doc)
-- programs fields
-- blocks (collection) <-- known collection name
--- block_1 (doc)
--- block_2 (doc)
--- block_3 (doc)
...etc
block_1, block_2 etc would be the document ID.
As far as I can see in the code you're writing multiple documents, but all to the same collection: Users/${userStore.uid}/programs/${programName}/blocks.
If you want to create multiple collections, you'll need to vary one of the odd-indexed parameters in this path, like blocks_1, blocks_2, etc. Note though that this is not recommended in most scenarios, as the client-side SDKs have no way to request a list of the collections under a specific path, so it's typically best to use hard-coded collection names - or collection names that are implicitly known in some other way.
So I found that my array I was looping over wasn't what I expected, nothing to do with Firebase. Fixed it further upstream and now I get the results I was after.
I have push notifications set up for my app using Firebase Cloud Functions. It works well. Now I want to update the app's badge count as part of the push notification. I've read that the only way to do that is via server-side code; I can't do it locally.
So I'm trying to get the number of new users from the server and then use that number as the badge count when I send the push notification, but I can't figure out how to go about it. I've spent three days on this and now I'm hoping someone can point me in the right direction.
I'm using Firebase functions and Typescript (with VSCode). My course of action is to:
get list of userIDs from 'admin' node
iterate over those userIDs on 'user' node to query if user's 'newUser' parameter is true
append those results to an array
count the array and then send that to the badge on push notification
My 'users' database structure is like so:
"users": {
"2NBvNgdNRVe3nccTEDts2Xseboma": {
"email": "someone#someone.com"
"newUser": "true",
"referral": "none",
...
},
"hjC6os6wzIV1FyULmGxalU3fM7ef": {
"email": "someoneElse#someone.com"
"newUser": "false",
"referral": "Bennett",
...
}
And my 'admin' database is structured like so:
"admin": {
"2NBvNgdNRVe3nccTEDts2Xseboma": {
"email": "someone#someone.com"
"familyName": "Someone",
"memberSince": "1529119893",
},
"hjC6os6wzIV1FyULmGxalU3fM7ef": {
"email": "someoneElse#someone.com"
"familyName": "Someone Else",
"memberSince": "1529125722",
...
}
Here is my feeble attempt to code this:
exports.getNewUserCount =
functions.database.ref('/users/{userID}/newUser')
.onUpdate((snapshot, _context) => {
console.log('test 2')
// Get a database reference
const db = admin.database();
const ref = db.ref('admin');
return ref.once('value', function(adminSnap) {
const userData = adminSnap.val()
console.log('admin key:', adminSnap.key)
console.log('user data:', userData)
})
});
Right now I'm stuck on retrieving the list of users from the admin node (my step #1 above).
UPDATE
I finally got a list of the users as a snapshot, but I can't figure out how to iterate over them. How do I turn the snapshot into an array of the user keys?
And then once I get the list of user keys, then how do I use that to iterate over the 'users' node to get the list of new users (my step #2 above)?
And then how to put those new users into an array (my step #3 above), and then get the number of new users for the 'badge' parameter when I send my push notification (my step #4 above)?
The problem is that this seems really inefficient. There has to be a better way to simply get a list of new users. There has to be some sort of query I can perform that will go over my 'users' node, see which ones have 'true' for their 'newUser' node, and get a count of those--instead of my roundabout way of getting a list of user from 'admin' node, then using that list to get a list of 'new users' from the 'users' node, then creating an array and then counting that array, then using that number to send to the 'badge' parameter on my push notification.
Any thoughts? I've been at this for days.
If it helps, I know Swift and the app is iOS. Thanks!!
UPDATE #2
So I opted to try and just get a snapshot of all users and bypass the 'admin' node altogether. Here is the code:
const db = admin.database();
const ref = db.ref('users');
return ref.once('value').then((adminSnap) => {
console.log('admin key:', adminSnap.key)
// create blank array to store
let newUserCount = 0;
// iterate over adminSnap to get each individual snap
adminSnap.forEach(function (userSnap) {
const userData = userSnap.val();
const userKey = userSnap.key
// console.log('email?', userData.email, 'user key:', userKey, 'new user?', userData.newUser)
if (userData.newUser === true) {
newUserCount++
console.log('new user:', userKey, userData.newUser, userData.email)
}
});
console.log(newUserCount)
})
This new code works and gives me the number for my badge parameter for when I perform my push notification, but I'm wondering if it's the most efficient way to do things. Plus, as my database grows in size, won't it tax the server / slow way down? And won't it cost me a lot of bandwidth for my Firebase account?
I thought this would be a simple thing to do, but it's turning into a bit of a hassle. I'm open to a different way to complete this. Thanks!
After even more research, I ended up abandoning my original approach. I decided to just create a new node on my Firebase database with the new user count and then update it via code from elsewhere. It's the simplest approach and will use the least amount of bandwidth.
Here is my final code:
function sendAlertToiPhone() {
console.log('test E')
// Get a database reference
const db = admin.database();
const ref = db.ref('stats');
ref.child('newUserCount').once('value').then((snapshot) => {
const newUserCount = snapshot.val()
console.log('new user count:', newUserCount)
// send to Phontaine's iPhone 6
const FCMToken = "blahbehtyblahblah"
const payload = {
notification: {
title: 'New User',
body: 'Moneypants has a new download.',
sound: 'default',
badge: String(newUserCount)
}
};
return admin.messaging().sendToDevice(FCMToken, payload)
.then(function (response) {
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
}).catch(function (err) {
console.log('new user count error:', err);
})
}
I am currently developing a server and one of its functions is retrieving data from the server when database, query, credentials, etc are supplied from the client. I am also using socket.io to stream 100 rows from server at a time.
Since I am not sure how many concurrent requests will be made to the database at once at peak, I have set up connectionPool using mssql node module. Below is succinctly how my code looks like.
socket.on('request:database', (connectionConf) => {
let query = connectionConf.query;
let dbAddress = connectionConf.dbAddress;
let id = connectionConf.id;
let password = connectionConf.password;
let dbName = connectionConf.dbName;
let requestedRows = parseInt(connectionConf.noOfRows);
let createExcel = connectionConf.excel;
const sqlConfig = {
user: id,
password: password,
server: dbAddress,
database: dbName,
pool: {
max: 1,
min: 0,
idleTimeoutMillis: 30000
},
options: {
encrypt: true,
}
};
let connectionPool = new sql.ConnectionPool(sqlConfig, err =>
request.stream = true;
request.query(query);
request.on('recordset', columns => {
//run functions with columns
socket.emit('response:headers', //send results);
// Emitted once for each recordset in a query
});
request.on('row', row => {
//Run some functions
socket.emit('result', //send 100 rows at a time);
// Emitted for each row in a recordset
});
)
});
In the above code, request:database socket.io event is triggered every time client makes a query. The problem is that I am not utilizing the pool correctly as I am creating new connectionPool every time client clicks send query button.
So, I cannot create connection pool prior to the button click because they are provided from the client
How do I resolve this connection pooling issue so that I can configure db connection with the data that is provided by the client and at the same time reuse same pool with several end point users?