I'm trying to make a bot that let's you give fame points to users as well as currency, currency works just fine and it updates whenever someone sends a message with a 1 minute cooldown. however I'm having problems with my fame schema. the bot creates a new schema if there's not an already existing one without problem and it also displays the amount correctly, however, when you click the button to give someone a Fame point, it doesn't, it stays at 0. I'm probably missing something simple but I can't seem to find it, here's the code:
const { MessageEmbed,ButtonInteraction} = require('discord.js');
const Fame = require('../../schemas/fame');
module.exports = {
data: {
name: `yes-fame`
},
async execute (interaction, client) {
const user = require('../../commands/entertainment/givefame')
const fameProfile = await client.createFame(user)
try {
await Fame.findOneAndUpdate({ _id: fameProfile._id}, { $inc: { amount: 1 } });
} catch (error) {
console.log(error);
}
const userEmbed = new MessageEmbed()
.setTitle(`<:fame:952026535756435536> Fame Point Given`)
.setDescription(`${interaction.user} has given 1 fame point to ${user} `)
.setTimestamp()
.setColor("#00FF00")
.setFooter(client.user.tag, client.user.displayAvatarURL());
await interaction.reply({ embeds: [userEmbed]});
}
};
(the cooldown is low because I'm not entirely sure how long to make it yet)
Here is the code for the Fame Schema.
const mongoose = require('mongoose');
const fameSchema = new mongoose.Schema ({
_id: mongoose.Schema.Types.ObjectId,
guildId: String,
memberId: String,
amount: { type: Number, default: 0}
});
module.exports = mongoose.model('Fame', fameSchema, 'fame-points');
and here's the code for the const "user", it's either the user mentioned or if none, the one using the slash command.
const user = interaction.options.getUser("user") || interaction.user;
And here's the createFame function
const Fame = require('../schemas/fame');
const mongoose = require('mongoose');
module.exports = (client) => {
client.createFame = async (member) => {
let fameProfile = await Fame.findOne({ memberId: member.id, guildId: member.guild.id });
if (fameProfile) {
return fameProfile;
} else {
fameProfile = await new Fame({
_id: mongoose.Types.ObjectId(),
guildId: member.guild.id,
memberId: member.id,
});
await fameProfile.save().catch(err => console.log(err));
return fameProfile;
}
};
};
I thought that maybe there was an error in the user const itself or when importing it but I made the bot send a test message using that const and it is getting the user no problem so idk what's wrong.
it shows the error:
TypeError: Cannot read properties of undefined (reading 'id')
at Client.client.createFame (C:\Users\xxx\OneDrive\desktop\bot\src\functions\createFame.js:6:89)
at Object.execute (C:\Users\xxx\OneDrive\desktop\bot\src\buttons\info\yes-fame.js:10:46)
at Object.execute (C:\Users\xxx\OneDrive\desktop\bot\src\events\interactionCreate.js:25:26)
at Client. (C:\Users\xxx\OneDrive\desktop\bot\src\functions\handleEvents.js:8:58)
There is a $inc property/method/wtv in mongoose model. Try this-
await Fame.findOneAndUpdate({ _id: fameProfile._id}, { $inc: { amount: 1 } });
Related
I sort of know why it happens, but not sure how to go on about solving it.
I have a React project that uses Cloud Firestore as database, and I have a simple login-page where you can sign in via your Google account. The first time you sign in a new document gets added to the "users" collection in Firebase.
After the document has been created it fetches that user data from Firebase and stores it in Redux.
const signInWithGoogle = async () => {
try {
const res = await signInWithPopup(auth, googleProvider);
const user = res.user;
const q = query(collection(db, "users"), where("uid", "==", user.uid));
const docs = await getDocs(q);
if(docs.docs.length === 0){
const firstName = user.displayName.split(' ')[0];
await addDoc(collection(db, "users"), {
uid: user.uid,
name: user.displayName,
firstName: firstName,
photoURL: user.photoURL,
authProvider: "google",
email: user.email,
})
dispatch(getUser(user))
}
} catch(err) {
console.error(err);
alert(err.message);
}
}
I also check whenever the user's auth state changes (here I also do another fetch and store it in Redux).
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
if(user){
dispatch(getUser(user))
} else {
console.log("user logout")
}
});
return unsubscribe;
}, []);
But when a new user signs in the first time, I get an error from the fetch:
export const getUser = createAsyncThunk("profile/getUser", async (user) => {
try {
const userQuery = query(
collection(db, "users"),
where("uid", "==", user?.uid)
);
const doc = await getDocs(userQuery);
const data = doc.docs[0].data();
return data;
} catch (err) {
console.error(err);
alert("An error occured while fetching user data");
}
});
"data" in above block is undefined for a small moment when the user signs in, so the alert in the try/catch block always goes off (it does manage to fetch the data after though).
This error only happens when it's a new user.
I understand that the fetch occurs before a document has been created in the "users" collection, but I'm not sure how to solve this. I've tried to add if/else to certain parts of the code (but just felt like I was grasping for straws).
I'm very much new to Firebase and still learning React, so every bit of help is really appreciated!
Problem is that your signInWithGoogle & useEffect both are running on user's auth status change. And, when its the new user, signInWithGoogle function makes aysnc call to create default doc, whereas useEffect runs to dispatch action, but at that moment user doesn't have any linked document. That is why you are getting undefined.
Ideally, you should remove one. You can merge the useEffect into signInWithGoogle to set the user details and dispatch as well.
const signInWithGoogle = async () => {
try {
const res = await signInWithPopup(auth, googleProvider);
const user = res.user;
const q = query(collection(db, "users"), where("uid", "==", user.uid));
const docs = await getDocs(q);
// create `doc` if its the new user
if(docs.docs.length === 0){
const firstName = user.displayName.split(' ')[0];
await addDoc(collection(db, "users"), {
uid: user.uid,
name: user.displayName,
firstName: firstName,
photoURL: user.photoURL,
authProvider: "google",
email: user.email,
})
}
// set user info and dispatch
setCurrentUser(user);
setLoading(false);
dispatch(getUser(user))
} catch(err) {
console.error(err);
alert(err.message);
}
}
Hope that answers your query.
Thanks
I have a collection of Posts that are added to my db via this method:
const sendPost = async (event: any) => {
event.preventDefault();
if (loading) return;
setLoading(true);
const docRef = await addDoc(collection(db, "Posts"), {
id: tokenId,
postId: UID,
username: username,
profilePic: uri,
bio: bio,
likes: 0,
text: input,
timestamp: serverTimestamp(),
});
I have created a delete button with an onClick handler with the following code:
const handleDelete = async (e:any) => {
e.stopPropagation();
deleteDoc(doc(db, "Posts", post.postId));
console.log(post.postId);
}
The console.log in the above code matches the UID of the Document that im trying to delete, but nothing happens. Note: I added delete to the rules, and still no luck.
Does anybody know if I'm missing a step or can point me in the right direction??
Visual
Thanks!
You just don't know what is doc ID. Using addDoc(collection(db, "Posts"), data) function, you generate random document ID, and it is not an UID! To get that ID, you need to show how you get data from Firebase.
Using getDocs function, you can get doc ID like this:
async function getDocuments() {
const ref = collection(db, 'Posts')
const result = await getDocs(ref)
let data = []
if(result.exists()) {
result.foreach(docSnap => {
const doc = docSnap.data()
doc.docID = docSnap.id // here you getting real document ID
data.push(doc)
}
)
}
}
You have document ID above + Start Collection blue button, not in postID field!
I'am having trouble figuring out how to delete a document from mongo DB after a timeout. Anyone can help me out with a simple way of doing it and maybe explain to me why this one is wrong? (it works, the document is deleted after some time , but I get a error message and the server stops)
this is the code I used written and also as a picture together with the terminal errormessage, I note, the document is deleted after the setTimeout runs out, but server stops:
documents are pretty simple consist of these:
server.js
import express from "express";
import cors from "cors";
import mongoose from "mongoose";
import shareRoutes from "./routes/shares.js";
const app = express();
app.use(cors());
app.get("/", (req, res) => {
res.json({ message: "API running..." });
res.end("");
});
app.use(express.json());
app.use("/radar", shareRoutes);
mongoose
.connect(
"mongodb+srv://<creditentials>#cluster0.dqlf2.mongodb.net/locations?retryWrites=true&w=majority",
{ useNewUrlParser: true },
{ useFindAndModify: false }
)
.then(() => {
app.listen(5000, () => {
"Server Running on port 5000";
});
})
.catch((err) => {
console.log(err);
});
shares.js for the route
import express from "express";
import {
createLocation,
getLocations,
} from "../controllers/shareController.js";
const router = express.Router();
// create location
router.post("/", createLocation);
// get Locations
router.get("/", getLocations);
export default router;
shareController.js
import express from "express";
import shareLocation from "../models/shareLocation.js";
const router = express.Router();
export const createLocation = async (req, res) => {
const { latitude, longitude, dateShared, timeShared, city, road } = req.body;
const newLocation = shareLocation({
latitude,
longitude,
dateShared,
timeShared,
city,
road,
});
try {
await newLocation.save();
res.status(201).json(newLocation);
setTimeout(() => {
(async () => {
try {
await shareLocation.findByIdAndRemove(newLocation._id);
res.json({ message: "Shared location deleted" });
} catch (error) {
res.status(409).json({ message: error });
}
})();
}, 30000);
} catch (error) {
res.status(409).json({ message: newLocation });
}
};
export const getLocations = async (req, res) => {
try {
const locations = await shareLocation.find({});
res.status(200).json(locations);
} catch (error) {
console.log(error);
res.status(409).json("Unable to fetch Locations");
}
};
export const deleteLocation = async (req, res) => {
const { id } = req.params;
try {
await shareLocation.findByIdAndRemove(id);
res.json({ message: "Shared location deleted" });
} catch (error) {
res.status(409).json({ message: error });
}
};
export default router;
shareLocations.js for the schema
import mongoose from "mongoose";
const hours = new Date().getHours().toLocaleString();
const minutes = new Date().getMinutes().toLocaleString();
const actualHours = hours.length < 2 ? "0" + hours : hours;
const actualMinutes = minutes.length < 2 ? "0" + minutes : minutes;
const locationSchema = mongoose.Schema({
timeShared: {
type: String,
default: actualHours + ":" + actualMinutes,
},
dateShared: {
type: String,
default: new Date().toDateString(),
},
latitude: {
type: String,
required: true,
},
longitude: {
type: String,
required: true,
},
city: {
type: String,
},
road: {
type: String,
},
});
export default mongoose.model("shareLocation", locationSchema);
I'll start with what is the "proper" solution, we'll take a look at what's wrong with the code after.
Mongo provides a built in way to remove documents after a certain time period, the "proper" way is to built a TTL index on the required field, and to specify after how many seconds you want that document deleted.
Mongo will then periodically check the index and clear documents when the time is up, this removes all kinds of levels of complexity from your app ( for example this timeout can easily cause a memory leak if too many calls are called in a short time window ).
A TTL index is created by using the simple createIndex syntax:
db.collection.createIndex( { "created_at": 1 }, { expireAfterSeconds: 30 } )
This will make documents expire 30 seconds after creation, you'll just have to add this timestamp to your code:
const newLocation = shareLocation({
latitude,
longitude,
dateShared,
timeShared,
city,
road,
created_at: new Date() // this new line is required
});
I can also tell you're using mongoose, then mongoose provides created_at field automatically if you set the Schema to include timestamps meaning your app can even ignore that.
Now what's wrong with your code?
It's simple, you first respond to the response in this line:
res.status(201).json(newLocation);
But then after a 30 second timeout you try to respond again, to the same response:
res.json({ message: "Shared location deleted" });
Node does not allow this behavior, you can only call set headers once ( which is called when responding ), I will not go into detail why as there are many stackoverflow answers (like this) that explain it.
Apart from the obvious issue that crashes your app, other issue's can arise from this code, as I mentioned before a memory leak can easily crash your app,
If your app restarts for whatever reason the locations that were "pending" in memory will not be cleared from the db, and more.
This is why it's recommended to let the DB handle the deletion.
I recently started working on discord ban bot with 3 main features:
Export IDs of all banned users in current Server/Guild.
Import IDs of banned users into current guild
Transfer ban list from current server to target server. (Under development)
None of the slash commands are working even though the logic is seemingly correct.
I'm following the discordjs guide & managed to make a Time Tag generator bot & this is my 2nd bot project. I admit I'm not familier with Javascript but the guide is very helpful nonetheless
Here is the export-ban-list code:
const { SlashCommandBuilder } = require('#discordjs/builders');
const { REST } = require('#discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const { token, pasteUser, pastePass, pasteKey } = require('../config.json');
const paste = require('better-pastebin');
const rest = new REST({ version: '9' }).setToken(token);
const date = new Date();
paste.setDevKey(pasteKey);
paste.login(pasteUser, pastePass);
function new_paste(serverName, results) {
const outputFile = `${serverName}-${date}.txt`;
paste.create({
contents: results,
name: outputFile,
expires: '1D',
anonymous: 'true',
},
function(success, data) {
if (success) {
return data;
}
else {
return 'There was some unexpected error.';
}
});
}
module.exports = {
data: new SlashCommandBuilder()
.setName('export-ban-list')
.setDescription('Exports ban list of current server'),
async execute(interaction) {
const bans = await rest.get(
Routes.guildBans(interaction.guildId),
);
await interaction.deferReply(`Found ${bans.length} bans. Exporting...`);
console.log(`Found ${bans.length} bans. Exporting...`);
let results = [];
bans.forEach((v) => {
results.push(v.user.id);
});
results = JSON.stringify(results);
const fe = new_paste(interaction.serverName, results);
return interaction.editReply(fe);
},
};
This command basically calculates the number of users banned, makes an array & exports it to pastebin.
The issue is, the bot code reaches till calculation part, but when it comes to making the list, console throws me errors:
Found 13 bans. Exporting...
DiscordAPIError: Cannot send an empty message
at RequestHandler.execute (D:\Github\Discord-Ban-Utils-Bot\node_modules\discord.js\src\rest\RequestHandler.js:298:13)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async RequestHandler.push (D:\Github\Discord-Ban-Utils-Bot\node_modules\discord.js\src\rest\RequestHandler.js:50:14)
at async InteractionWebhook.editMessage (D:\Github\Discord-Ban-Utils-Bot\node_modules\discord.js\src\structures\Webhook.js:311:15)
at async CommandInteraction.editReply (D:\Github\Discord-Ban-Utils-Bot\node_modules\discord.js\src\structures\interfaces\InteractionResponses.js:137:21)
at async Client.<anonymous> (D:\Github\Discord-Ban-Utils-Bot\index.js:41:3) {
method: 'patch',
path: '/webhooks/897454611370213436/aW50ZXJhY3Rpb246ODk4ODkyNzI0NTcxMzczNjA5OmtPeGtqelQ5eUFhMnNqVzc1Q3BpMWtQZUZRdVhveGQxaHFheFJCdVFoUWNxNUk5TVpGbThEQjdWcDdyaHZyaUJPeUpsRWFlbUp0WnVLYjB5V0RtYmJCSmlNU2wwUVlka1hYMHg0bHRJbzlHelVwRmJ6VUpRaXF2YktaVDN1ZlVp/messages/#original',
code: 50006,
httpStatus: 400,
requestData: {
json: {
content: undefined,
tts: false,
nonce: undefined,
embeds: undefined,
components: undefined,
username: undefined,
avatar_url: undefined,
allowed_mentions: undefined,
flags: undefined,
message_reference: undefined,
attachments: undefined,
sticker_ids: undefined
},
files: []
}
}
D:\Github\Discord-Ban-Utils-Bot\node_modules\discord.js\src\structures\interfaces\InteractionResponses.js:89
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
^
Error [INTERACTION_ALREADY_REPLIED]: The reply to this interaction has already been sent or deferred.
at CommandInteraction.reply (D:\Github\Discord-Ban-Utils-Bot\node_modules\discord.js\src\structures\interfaces\InteractionResponses.js:89:46)
at Client.<anonymous> (D:\Github\Discord-Ban-Utils-Bot\index.js:45:22)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
[Symbol(code)]: 'INTERACTION_ALREADY_REPLIED'
}
Thanks to Jim I used the console.log() to check what was going on.
And indeed the data from function inside new_paste() wasn't being returned to fe.
(I had messed up the return scopes basically)
Here is the final code after fixes & scope resolutions
const { SlashCommandBuilder } = require('#discordjs/builders');
const { REST } = require('#discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const { token, pasteUser, pastePass, pasteKey } = require('../config.json');
const paste = require('better-pastebin');
const rest = new REST({ version: '9' }).setToken(token);
const date = new Date();
paste.setDevKey(pasteKey);
paste.login(pasteUser, pastePass);
module.exports = {
data: new SlashCommandBuilder()
.setName('export-ban-list')
.setDescription('Exports ban list of current server'),
async execute(interaction) {
const bans = await rest.get(
Routes.guildBans(interaction.guildId),
);
await interaction.deferReply(`Found ${bans.length} bans. Exporting...`);
console.log(`Found ${bans.length} bans. Exporting...`);
let results = [];
bans.forEach((v) => {
results.push(v.user.id);
});
results = JSON.stringify(results);
console.log(results);
const outputFile = `${interaction.guild.name}-${date}.txt`;
paste.create({
contents: results,
name: outputFile,
expires: '1D',
anonymous: 'true',
},
function(success, data) {
if (success) {
return interaction.editReply(data);
}
else {
return interaction.editReply('There was some unexpected error.');
}
});
},
};
And finally I get the proper pastebin url as output.
Code hosted here
I think your npm package better-pastebin has an error. I am not familiar with that npm package, so I can’t determine whether it has an error for you, but I think if you change the npm package, the error will not appear.
I'm trying to make a ticket system with discord.js v13, but the ephemeral method doesn't work and, when my bot turn on, i need to click one time in the button to activate the system.
print
My code:
const { MessageActionRow, MessageButton, MessageEmbed, Permissions } = require('discord.js');
const db = require("quick.db");
exports.run = async (client, interaction) => {
if (!interaction.isButton()) return;
interaction.deferUpdate();
let getContador = await db.get('counter')
if(getContador === null) {
let contador = await db.set("counter", 1)
}
await db.add("counter", 1)
let tcID = "895323735702253569"
let tmID = "895358127950659604"
const filter = i => i.customId === 'OPENTICKET'
const collector = interaction.channel.createMessageComponentCollector({ filter, max: 1 });
collector.on("collect", async i => {
let cTicket = await i.guild.channels.create(`🎫┆Ticket ${getContador}`, {
type: 'GUILD_TEXT',
permissionOverwrites: [
{
id: i.guild.id,
deny: [Permissions.FLAGS.VIEW_CHANNEL],
},
{
id: i.user.id,
allow: [Permissions.FLAGS.VIEW_CHANNEL, Permissions.FLAGS.SEND_MESSAGES],
},
]
})
await interaction.channel.messages.cache.get(tmID).reply({ content: `... ${cTicket.toString()}...`, ephemeral: true })
})
}
You are replying to a normal message. If you would like an ephemeral message, you have to directly reply to the interaction.
interaction.reply({
content: `... ${cTicket.toString()}...`,
ephemeral: true
})