Function that gets past messages stopped working. Any way to fix? - node.js

I got this function from another stackoverflow question. I fixed it to work with Discord.js v12 by changing channel.fetchMessages to channel.messages.fetch. The function worked at first and everything was fine, but then one time when I started up my program it started showing this error: "TypeError: Cannot read property 'id' of undefined" This error occurs at line 55 which is last_id = messages.last().id; I did not change the function at all and it just stopped working. Any ideas?
async function lots_of_messages_getter(channel, limit = 6000) {
const sum_messages = [];
let last_id;
while (true) {
const options = { limit: 100 };
if (last_id) {
options.before = last_id;
}
const messages = await channel.messages.fetch(options);
sum_messages.push(...messages.array());
last_id = messages.last().id;
if (messages.size != 100 || sum_messages >= limit) {
break;
}
}
return sum_messages;
}

becuase sum_messages will never be greater or same as limit because it's not a number, it must be sum_messages.length and a check after getting the messages if(messages.size === 0) would also not hurt
async function lots_of_messages_getter(channel, limit = 6000) {
const sum_messages = [];
let last_id;
while (true) {
const options = { limit: 100 };
if (last_id) {
options.before = last_id;
}
const messages = await channel.messages.fetch(options);
if (messages.size === 0) {
break;
}
sum_messages.push(...messages.array());
last_id = messages.last().id;
if (messages.size != 100 || sum_messages.length >= limit) {
break;
}
}
return sum_messages;
}

Related

Best practice using promises chaining in node JS

I'm little bit confusing in promises. first, I have some ugly code like this:
async function presence(ctx) {
try {
var prsenceData = [];
var isSuccess = Boolean(false);
var ckFilePath = "./somepath/cookie.json";
if (!fs.existsSync(ckFilePath)) {
await menuLogin.login(ctx).then(login => {
isSuccess = Boolean(login[0].status);
myCk.saveCookies(login[0].cookies, ckFilePath);
if (!isSuccess) {
myCk.deleteCookies(ckFilePath);
return false;
}
});
} else {
await myCk.checkToDelete(ckFilePath).then(isDel => {
if (isDel) {
return false;
}
});
}
await presenceNow.check(fs.existsSync(ckFilePath), ctx).then(data => {
for (let id = 0; id < data[0].pesan.length; id++) {
console.log(data[0].pesan[id]);
}
for (let id = 0; id < data[0].id.length; id++) {
presenceData.push(data[0].id);
}
if (data[0].pesan.length == 0 && fs.existsSync(ckFilePath)) {
myCk.deleteCookies(ckFilePath);
}
});
} catch (e) {
console.log(e);
}
return presenceData;
}
Can anyone explain why presenceNow.check() function is not calling if my ckFilePath does not exist? but if myCkFilePath is exist, my code run so well. And maybe anyone can show me the better code for that case? thanks.
Mixing async/await and promise chains like this is something of a code smell that the author lacked an understand of async/await. It's also something of a mixed metaphor.
If you refactor it to actually use async/await you get something like this that's a lot easier to understand.
My suspicion is that your presenceNow.check() method is not being called because the function is taking returning via one of the two return paths above it:
the file exists and myCk.checkToDelete() returns true, or
the file does not exist, and the login is unsuccessful.
const fs = require('fs/promises');
async function presence(ctx) {
var presenceData = [];
var isSuccess = false;
var ckFilePath = "./somepath/cookie.json";
let ckFilePathExists = await fs.access(ckFilePath);
if (ckFilePathExists) {
const isDel = await myCk.checkToDelete(ckFilePath);
if (isDel) {
return false;
}
} else {
const login = await menuLogin.login(ctx);
const isSuccess = login[0].status
myCk.saveCookies(login[0].cookies, ckFilePath);
if (!isSuccess) {
myCk.deleteCookies(ckFilePath);
return false;
}
}
ckFilePathExists = await fs.access(ckFilePath)
const data = await presenceNow.check(ckFilePathExists, ctx);
for (let id = 0; id < data[0].pesan.length; id++) {
console.log(data[0].pesan[id]);
}
for (let id = 0; id < data[0].id.length; id++) {
presenceData.push(data[0].id);
}
if (data[0].pesan.length == 0 && await fs.access(ckFilePath) ) {
myCk.deleteCookies(ckFilePath);
}
return presenceData;
}

UnhandledPromiseRejectionWarning: TypeError: message.channel.bulkDelete is not a function

For some reason, I get that error when using bulkDelete()
It was working recently. (I didn't update it.)
Code:
module.exports = {
name: 'clear',
description: 'Clears messages',
async execute(message, args) {
if (!args[0]) return message.reply("Specify Amount");
if (isNaN(args[0])) return message.reply("Enter a real number");
if (args[0] > 100) return message.reply("100 is the max number of messages to clear");
if (args[0] < 1) return message.reply("1 is the minimum number of messages to clear");
var clrMsg = (args[0]) - -1; // (using +1 puts a 1 infront of the number)
if (clrMsg == 101) {
clrMsg = 100;
}
await message.channel.messages.fetch({
limit: args[0]
}).then(messages => {
message.channel.bulkDelete(clrMsg);
});
}
}
UPDATE: I'm getting the same error when it was just working. I retyped the entire code and it still returns that error.
There seems two issues with the code: the way you are defining method and using then with await. You should do something like this:
module.exports = {
name: 'clear',
description: 'Clears messages',
execute: async (message, args) => {
if (!args[0]) return message.reply("Specify Amount");
if (isNaN(args[0])) return message.reply("Enter a real number");
if (args[0] > 100) return message.reply("100 is the max number of messages to clear");
if (args[0] < 1) return message.reply("1 is the minimum number of messages to clear");
var clrMsg = (args[0]) - -1; // (using +1 puts a 1 infront of the number)
if (clrMsg == 101) {
clrMsg = 100;
}
try {
const messages = await message.channel.messages.fetch({
limit: args[0]
})
message.channel.bulkDelete(clrMsg); // it seems message doesn't have the function, you might need messages instead
} catch (e) {
console.log(`Error is: ${e}`);
}
}
}
Always use try/catch with async/await.
Still no error with the code. I retyped the exact same thing and it works. Probably a bug with discord.js. New code:
message.channel.bulkDelete(clrMsg);

Discord.js - counter of sent messages, DM to all members of guild

So, I have a working example of code, that sends a DM to all members of a guild.
However, I want the whole thing to end with a message, sort of: "Successfully sent XX messages".
const sentMessages = 0;
if (!message.member.hasPermission(`ADMINISTRATOR`)) {
message.channel.send(`You don't have permission to use this command!`);
message.client.channels.cache.get(logchannel).send(`${message.author} tried using DMALL! ` + dato.toLocaleTimeString() + dato.toLocaleDateString());
return;
} else {
const delay = (msec) => new Promise((resolve) => setTimeout(resolve, msec));
const sendMessage = args.join(" ");
message.channel.send(`Sending messages, please wait...`);
await delay(1000);
let interval = 500; // how much time should the delay between two iterations be (in milliseconds)?
let promise = Promise.resolve();
message.guild.members.cache.forEach(function (user) {
promise = promise.then(function () {
if (user.id != message.client.user.id) {
user.send(sendMessage);
sentMessages++;
return new Promise(function (resolve) {
setTimeout(resolve, interval);
});
}
});
message.channel.send(`Successfully sent ${sentMessages} messages!`);
});
The message at the end (Successfully...) keeps returning 0, no matter when or where I place it.
If I put a line like: message.channel.send(`Sent: ${sentMessages}); below sentMessages++; I get an incrementing number like I expect.
What and where am I doing this wrong?
Ended up turning to a friend who is much better at coding than me.
He came up with a totally different way of getting there:
if (!message.member.hasPermission(`ADMINISTRATOR`)) {
message.channel.send(`You don't have permission to use this command!`);
message.client.channels.cache.get(logchannel).send(`${message.author} tried using DMALL in **${message.guild.name}**!` + dato.toLocaleTimeString() + dato.toLocaleDateString());
return;
} else {
const delay = (msec) => new Promise((resolve) => setTimeout(resolve, msec));
const sendMessage = args.join(" ");
message.channel.send(`Sending messages, please wait...`);
await delay(1000);
const interval = 500;
let array;
try {
const all = await message.guild.members.fetch();
array = all.array();
} catch {
array = message.guild.members.cache.array();
}
let sentMessages = 0;
for (var i = 0; i < array.length; i++) {
const user = array[i];
if (!user.bot) {
try {
await user.send(sendMessage);
sentMessages++;
await delay(interval);
} catch { }
}
}
message.client.channels.cache.get(logchannel).send(`${message.author} sent a DM to all members of: **${message.guild.name}**!` + dato.toLocaleTimeString() + dato.toLocaleDateString());
message.channel.send(`Finished sending messages to all users, ${sentMessages} messages sent!`);
}
This code is tested and found to be working like a charm
Use let sentMessages = 0 instead of const sentMessages = 0
Constants can't be updated or reassigned, so sentMessages++ won't do anything
Just do not do this if you do not want to be bannes from Discord, it is forbidden to send private message to all members of a guild.

Complex query on more than one child in cloud functions

Here is the structure of my firebase database:
/UserData
/DeviceMgmt
/Counters
/NumberOfAll:
/NumberOfSelected
/TotalDownloaded
...
/Devices
/pushId1
/uid
/signOutTime
/toSelect=true (optional)
/downloaded
/lastDownload
/pushId2
/pushId3
...
And this is my cloud function:
exports.markDevicesForDownload = functions.database.ref('/UserData/DeviceMgmt/Counters/NumberOfSelected').onUpdate( (change) => {
const changeRef = change.after.ref;
const deviceMgmtRef = changeRef.parent.parent; // /UserData/DeviceMgmt
if (change.after.val() === 0 ) { //NumberOfSelected gets 0 value
return deviceMgmtRef.once('value')
.then((snap) => {
const devicesRef = snap.child('Devices').ref;
var average;
var numberOfAllDevices;
var totalDownloaded;
numberOfAllDevices = snap.child('Counters/NumberOfAll').val();
totalDownloaded = snap.child('Counters/TotalDownloaded').val();
average = Math.round(totalDownloaded/numberOfAllDevices);
return devicesRef
.orderByChild('signOutTime')
.equalTo(0)
.once('value',(devices) => {
return devices.ref
.orderByChild('downloaded')
.endAt(average)
.once('value',(devices) => {
devices.forEach((device) => {
device.child('toSelect').ref.set(true);
});
});
});
});
} else {
return false;
}
});
The function triggers when the counter NumberOfSelected = 0;
This happens when under any of device pushId there is no child toSelect. Then the query on downloaded child makes all devices with downloaded less than average set toSelect=true.
I wanted to limit the devices only to those which have signOutTime equal 0.
Somehow that query does not work and all devices are considered.
What I did wrong???
I would push all async tasks into a promise array and then return them all when all tasks complete:
exports.markDevicesForDownload = functions.database.ref('/UserData/DeviceMgmt/Counters/NumberOfSelected').onUpdate((change) => {
const changeRef = change.after.ref;
const deviceMgmtRef = changeRef.parent.parent; // /UserData/DeviceMgmt
if (change.after.val() === 0) { //NumberOfSelected gets 0 value
return deviceMgmtRef.once('value')
.then((snap) => {
const promises = [];
const devicesRef = snap.child('Devices').ref;
var average;
var numberOfAllDevices;
var totalDownloaded;
numberOfAllDevices = snap.child('Counters/NumberOfAll').val();
totalDownloaded = snap.child('Counters/TotalDownloaded').val();
average = Math.round(totalDownloaded / numberOfAllDevices);
const dR = devicesRef
.orderByChild('signOutTime')
.equalTo(0)
.once('value', (devices) => {
const dW = devices.ref
.orderByChild('downloaded')
.endAt(average)
.once('value', (devices) => {
devices.forEach((device) => {
if (device.child("signOutTime").val() === 0){
promises.push(device.child('toSelect').ref.set(true));
}
});
});
promises.push(dW);
});
promises.push(dR);
return Promise.all(promises);
});
} else {
return false;
}
});

ESOCKETTIMEDOUT Cloud Functions for Firebase

I am using Cloud Functions for Firebase together with my Firebase Realtime Database in order to do some data management for my app.
One of my functions though seems to get terminated since it takes about 100-150 seconds to complete. This happens with error : ESOCKETTIMEDOUT.
Is there a way to prevent this?
Here is my function:
function getTopCarsForUserWithPreferences(userId, genres) {
const pathToCars = admin.database().ref('cars');
pathTocars.orderByChild("IsTop").equalTo(true).once("value").then(function(snapshot) {
return writeSuggestedCars(userId, genres, snapshot);
}).catch(reason => {
console.log(reason)
})
}
function writeSuggestedCars(userId, genres, snapshot) {
const carsToWrite = {};
var snapCount = 0
snapshot.forEach(function(carSnapshot) {
snapCount += 1
const carDict = carSnapshot.val();
const carGenres = carDict.taCarGenre;
const genre_one = genres[0];
const genre_two = genres[1];
if (carGenres[genre_one] === true ||carGenres[genre_two] == true) {
carsToWrite[carSnapshot.key] = carDict
}
if (snapshot.numChildren() - 1 == snapCount) {
const pathToSuggest = admin.database().ref('carsSuggested').child(userId);
pathToSuggest.set(carsToWrite).then(snap => {
}).catch(reason => {
console.log(reason)
});
}
});
}
The getTopCarsForUserWithPreferences gets called when a user adds preferences. Also the cars table has about 50k entries.
Well you need to return everytime you use a async task.
Edit: you return 'writeSuggestedCars' but I think it never returns a value. I do not have a compiler, but I thought it was return Promise.resolved(). Can you insert it where I putted 'HERE'?
Maybe this will work:
function getTopCarsForUserWithPreferences(userId, genres) {
const pathToCars = admin.database().ref('cars');
return pathTocars.orderByChild("IsTop").equalTo(true).once("value").then(function(snapshot) {
return writeSuggestedCars(userId, genres, snapshot);
}).catch(reason => {
console.log(reason)
})
}
function writeSuggestedCars(userId, genres, snapshot) {
const carsToWrite = {};
var snapCount = 0
snapshot.forEach(function(carSnapshot) {
snapCount += 1
const carDict = carSnapshot.val();
const carGenres = carDict.taCarGenre;
const genre_one = genres[0];
const genre_two = genres[1];
if (carGenres[genre_one] === true ||carGenres[genre_two] == true) {
carsToWrite[carSnapshot.key] = carDict
}
if (snapshot.numChildren() - 1 == snapCount) {
const pathToSuggest = admin.database().ref('carsSuggested').child(userId);
return pathToSuggest.set(carsToWrite).then(snap => {
// 'HERE' I think return promise/Promise.resolve() will work
}).catch(reason => {
console.log(reason)
});
}
});
}

Resources