So I am having some issues with creating my bot. All I am trying to have it do, is keep track of a list of players that type the command "+me" to join a "waiting list", "-me" in order to be removed from the list, and "?list" in order to display the list. I plan to add other commands later.
The issue I am having, is that the command works fine to add someone to the list, however after the first command the bot stops responding to commands. This leaves me able to add myself to the queue, but then not able to leave, no one else able to join, not able to list it, etc.
Also if you could mention a way to move the for loop I am using to display the list into a seperate function I would greatly appreaciate it. I am new to Javascript and my attempts are crashing it for some reason.
const Discord = require('discord.js');
const {prefix, token} = require('./config.json');
const client = new Discord.Client();
var rankedList = []; // Initialise an empty array
client.login(token);
client.once('ready', () =>{
console.log('Ready!');
})
client.once('message', message =>{
// Add to queue
if(message.content.startsWith(`${prefix}me`)){
console.log(message.author + "added to queue.");
message.channel.send(message.author + " added to queue.");
var temp = message.author;
rankedList.push(temp); // the array will dynamically grow
// Show queue after adding
//for (var i = 0; i < rankedList.length; i++) {
// message.channel.send(i+1 + "- " + rankedList[i]);
//}
message.channel.send(`${rankedList.map((player,index) => `${index+1} - ${player}`).join('n')}`);
}
// Remove from queue
if(message.content.startsWith(`-me`)){
console.log(message.author + "removed from queue.");
message.channel.send(message.author + " removed from queue.");
for( var i = 0; i < rankedList.length; i++){
if ( rankedList[i] === message.author) {
rankedList.splice(i, 1);
i--;
}
}
// Show queue after removing
for (var i = 0; i < rankedList.length; i++) {
message.channel.send(i+1 + "- " + rankedList[i]);
}
}
if(message.content.startsWith(`?list`)){
console.log(message.author + "displayed the queue.");
// Show list
for (var i = 0; i < rankedList.length; i++) {
message.channel.send(i+1 + "- " + rankedList[i]);
}
}
})
The issue could be you have used "once" instead of "on". The latter triggers event every time, while the former listens once.
// Add to queue
client.on('message', async (message) => {
// It's good practice to ignore other bots. This also makes your bot ignore itself and not get into a spam loop
if(message.author.bot) return;
if(message.content.startsWith(`${prefix}me`)){
var temp = message.author;
rankedList.push(temp); // the array will dynamically grow
console.log(message.author + "added to queue.");
message.channel.send(`${message.author} added to queue.
${rankedList.map((player,index) => `${index+1} - ${player}`).join('
n')}`);
}
});
You are basically sending the entire message at once. I could see that the send() function is asynchronous [ https://discord.js.org/#/docs/main/stable/class/DMChannel?scrollTo=send ]and should not be chained in a for loop like above. The above code is also more concise
I have used template literals [ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals ] to remove multiple concatenation.
Related
So, I am making this discord bot and I wanted to make it so if someone's message gets deleted, It can be stored onto a .txt file, And if someone wanted, They could just type in a command and the bot will show the list of deleted messages.
So, when the command is fired, it will need a number parameter and that parameter will be the number of messages that will be shown.
Let's call that number numberOfSnipes
The code will get the .txt file and turn it into an array by splitting each line and putting it in one array.
As so,
fs.readFile('deletedMsgs/' + message.guildId + '.txt', function (err, data) {
messagesArray = data.toString().split('\n');
});
As the code says, It will be called messagesArray
Now the problem comes here, We will need to iterate through the array and get the number of messages needed, This will be put into another array which will be called messagesToShow
So, I tried coding that and failed,
Code:
messagesToShow = []
for (let i = messagesArray.length; i < messagesArray.length - numberOfSnipes; i--) {
console.log(i)
messagesToShow.push(messagesArray[i])
}
FYI, console.log(i) did not log anything.
I tried to log messagesArray, It logged an empty array
Keep in mind that numberOfSnipes and messagesArray were both logged and they returned the right information.
Since, This can be more easier for you all. To solve, I will provide the whole code:
if (message.content.toLowerCase().startsWith(config.prefix + 'snipelist')) {
numberOfSnipes = parseInt(message.content.split(' ')[1]);
if (fs.existsSync('deletedMsgs/' + message.guildId + '.txt')) {
fs.readFile(
'deletedMsgs/' + message.guildId + '.txt',
function (err, data) {
messagesArray = data.toString().split('\n');
console.log(messagesArray);
if (numberOfSnipes > messagesArray.length) {
message.reply('Unable to return messages.');
} else {
messagesToShow = [];
for (
let i = messagesArray.length;
i < messagesArray.length - numberOfSnipes;
i--
) {
console.log(i);
messagesToShow.push(messagesArray[i]);
}
console.log(messagesToShow);
finalMessage = ' ';
for (let letter in messagesToShow.toString()) {
if (letter != ',') {
finalMessage += letter;
} else if (letter == ',') {
finalMessage += '\n';
}
}
message.reply(finalMessage);
}
}
);
}
}
Any help is appreciated.
I have two functions that I am trying to run when I load the page. dataRetrieve() gets the data from a firebase collection. populate() is supposed to populate a boxlist with the entries retrieved from dataRetrieve(). The main problem is that it lists the array as empty when I run populate() after dataRetrieve() no matter what I try. The last thing I tried was this:
async function dataRetrieve(){
const getAdmins = firebase.functions().httpsCallable('getAdmins');
// Passing params to data object in Cloud functinon
getAdmins({}).then((results) => {
admins = results;
console.log("admins retrieved");
console.log(admins);
}).then(() => {
populate();
});
}
async function populate(){
let list = document.getElementById("user-list");
//loop through users in out Users object and add them to the list
for (var i = 0; i < admins.length; i++) {
let newItem = document.createElement('option');
newItem.innerHTML = admins[i].first + " " +admins[i].last;
newItem.id = admins[i].uid;
if (i == 0) {
newItem.className = "active";
}
console.log(newItem.innerHTML + " " + newItem.id)
list.appendChild(newItem);
}
updateResponse(list.firstChild);
list.size = admins.length;
console.log(document.getElementById("user-list").size)
//collect all the list items
let listItems = list.querySelectorAll('option');
//loop through the list itmes and add a click listener to each that toggles the 'active' state
for (var i = 0; i < listItems.length; i ++) {
listItems[i].addEventListener('click', function(e) {
if (!e.target.classList.contains('active')) {
for (var i = 0; i < listItems.length; i ++) {
listItems[i].classList.remove('active');
}
e.target.classList.add('active');
updateResponse(e.target);
}
})
}
}
also, admins is a global variable listed at the start of the script:
var admins = [];
I am trying to run all this onload so I can instantly generate the list
I thought that .next would cause it to wait to get the values before running, but even making results a parameter and transferring it directly into the function that way gives an undefined array. I don't understand why the function insists on calling on old data. Pls help.
I'm not sure what updateResponse function does. If it's not returning a promise then I'd make the populate function synchronous first. Also do you really need to use admins array somewhere else apart from populate function that it is a global variable? If not then I'd just pass it as a parameter.
async function dataRetrieve() {
const getAdmins = firebase.functions().httpsCallable('getAdmins');
// Passing params to data object in Cloud function
const results = await getAdmins({})
console.log("admins retrieved");
console.log(results);
// Passing results in populate function
populate(results.data)
// If your function returns an array, pass the array itself
}
function populate(admins) {
let list = document.getElementById("user-list");
//loop through users in out Users object and add them to the list
// Using a for-of loop instead so no need to worry about checking the length here
for (const admin of admins) {
let newItem = document.createElement('option');
newItem.innerHTML = admin.first + " " + admin.last;
newItem.id = admin.uid;
//if (i == 0) {
// newItem.className = "active";
//}
console.log(newItem.innerHTML + " " + newItem.id)
list.appendChild(newItem);
}
updateResponse(list.firstChild);
// rest of the logic
}
I guess you know how to check when the page loads. call the retrieve function when the page is loaded. Then you should call the populate function at the end of the retrieve function. this makes sure that the populate function is called after you get all the data
I'm wondering how I could delete messages using a command like !clear [number] and have those messages fetched back to a channel including:
The user ID who used the command to delete the message
The user ID of who said the deleted message(s)
The content of the message(s)
The channel it has been deleted from
The timestamp of the message
All this stuff in a discord embed.
I'm relatively new to coding, and I'm developing this bot for a server with 40,000 people and we need to keep logs of all deleted messages.
Please, someone, help me out. I would greatly appreciate it :D. If needed I can explain in further detail if you still aren't sure of what I'm looking to do with this bot :D
To delete messages with a command, you have to use TextChannel.bulkDelete(), in this case maybe after fetching the messages via TextChannel.fetchMessages().
To "log" them, you may want to build a RichEmbed and put your info in the fields.
I would try something like this:
// ASSUMPTIONS:
// logs = the TextChannel in wich you want the embed to be sent
// trigger = the Messages that triggered the command
//
// This is just a sample implementation, it could contain errors or might not be the fastest
// Its aim is not to make it for you, but to give you a model
async function clear(n = 1, logs, trigger) {
let {channel: source, author} = trigger;
if (n < 1 || !logs || !source) throw new Error("One of the arguments was wrong.");
let coll = await source.fetchMessages({ limit: n }), // get the messages
arr = coll.array(),
collected = [],
embeds = [];
// create groups of 25 messages, the field limit for a RichEmbed
let index = 0;
for (let i = 0; i < arr.length; i += 25) {
collected.push([]);
for (let m = i; m < i + 25; m++)
if (arr[m]) collected[index].push(arr[m]);
index++;
}
// for every group of messages, create an embed
// I used some sample titles that you can obviously modify
for (let i = 0; i < collected.length; i++) {
let embed = new Discord.RichEmbed()
.setTitle(`Channel cleaning${collected.length > 1 ? ` - Part ${i+1}` : ""}`)
.setDescription(`Deleted from ${source}`)
.setAuthor(`${author.tag} (${author.id})`, author.displayAvatarURL)
.setTimestamp(trigger.editedAt ? trigger.editedAt : trigger.createdAt),
group = collected[i];
for (let msg of group) {
let a = `${msg.author.tag} (${msg.author.id}) at ${msg.editedAt ? msg.editedAt : msg.createdAt}`,
c = msg.content.length > 1024 ? msg.content.substring(0, msg.content.length - 3) + '...' : msg.content;
embed.addField(a, c);
}
embeds.push(embed);
}
// once the embeds are ready, you can send them
source.bulkDelete(coll).then(async () => {
for (let embed of embeds) await source.send({embed});
}).catch(console.error);
}
// this is a REALLY basic command implementation, please DO NOT use this as yours
client.on('message', async msg => {
let command = 'clear ';
if (msg.content.startsWith(command)) {
let args = msg.content.substring(command.length).split(' ');
if (isNaN(args[0]) || parseInt(args[0]) < 1) msg.reply(`\`${args[0]}\` is not a valid number.`);
else {
await msg.delete(); // delete your message first
let logs; // store your channel here
clear(parseInt(args[0]), logs, msg);
}
}
});
Note: the highlighting for this kind of stuff, with a lot of backtick strings and objects, is pretty bad. I suggest reading the code in another editor or you might end up not understanding anything
I think a firebase function updating a list that I have in the firebase database is being captured by a subscription that is subscribed to that list. From what the list output looks like on my phone (in the app)...and from what my console output looks like (the way it repeats) it seems like it is capturing the whole list and displaying it each time one is added. So (I looked this up)...I believe this equation represents what is happening:
(N(N + 1))/2
It is how you get the sum of all of the numbers from 1 to N. Doing the math in my case (N = 30 or so), I get around 465 entries...so you can see it is loading a ton, when I only want it to load the first 10.
To show what is happening with the output here is a pastebin https://pastebin.com/B7yitqvD.
In the output pay attention to the array that is above/before length - 1 load. You can see that it is rapidly returning an array with one more entry every time and adding it to the list. I did an extremely rough count of how many items are in my list too, and I got 440...so that roughly matches the 465 number.
The chain of events starts in a page that isn't the page with the list with this function - which initiates the sorting on the firebase functions side:
let a = this.http.get('https://us-central1-mane-4152c.cloudfunctions.net/sortDistance?text='+resp.coords.latitude+':'+resp.coords.longitude+':'+this.username);
this.subscription6 = a.subscribe(res => {
console.log(res + "response from firesbase functions");
loading.dismiss();
}, err => {
console.log(JSON.stringify(err))
loading.dismiss();
})
Here is the function on the page with the list that I think is capturing the entire sort for some reason. The subscription is being repeated as the firebase function sorts, I believe.
loadDistances() {
//return new Promise((resolve, reject) => {
let cacheKey = "distances"
let arr = [];
let mapped;
console.log("IN LOADDISTANCES #$$$$$$$$$$$$$$$$$$$$$");
console.log("IN geo get position #$$$$$$$5354554354$$$$$$$");
this.distancelist = this.af.list('distances/' + this.username, { query: {
orderByChild: 'distance',
limitToFirst: 10
}});
this.subscription6 = this.distancelist.subscribe(items => {
let x = 0;
console.log(JSON.stringify(items) + " length - 1 load");
items.forEach(item => {
let storageRef = firebase.storage().ref().child('/settings/' + item.username + '/profilepicture.png');
storageRef.getDownloadURL().then(url => {
console.log(url + "in download url !!!!!!!!!!!!!!!!!!!!!!!!");
item.picURL = url;
}).catch((e) => {
console.log("in caught url !!!!!!!$$$$$$$!!");
item.picURL = 'assets/blankprof.png';
});
this.distances.push(item);
if(x == items.length - 1) {
this.startAtKey4 = items[x].distance;
}
x++;
})
//this.subscription6.unsubscribe();
})
}
The subscription in loadDistances function works fine as long as I don't update the list from the other page - another indicator that it might be capturing the whole sort and listing it repeatedly as it sorts.
I have tried as as I could think of to unsubscribe from the list after I update...so then I could just load the list of 10 the next time the page with the list enters, instead of right after the update (over and over again). I know that firebase functions is in beta. Could this be a bug on their side? Here is my firebase functions code:
exports.sortDistance = functions.https.onRequest((req, res) => {
// Grab the text parameter.
var array = req.query.text.split(':');
// Push the new message into the Realtime Database using the Firebase Admin SDK.
// Get a database reference to our posts
var db = admin.database();
var ref = db.ref("profiles/stylists");
var promises = [];
// Attach an asynchronous callback to read the data at our posts reference
ref.on("value", function(snapshot) {
//console.log(snapshot.val());
var snap = snapshot.val();
for(const user in snap) {
promises.push(new Promise(function(resolve, reject) {
var snapadd = snap[user].address;
console.log(snapadd + " snap user address (((((((())))))))");
if(snapadd != null || typeof snapadd != undefined) {
googleMapsClient.geocode({
address: snapadd
}).asPromise()
.then(response => {
console.log(response.json.results[0].geometry.location.lat);
console.log(" +++ " + response.json.results[0].geometry.location.lat + ' ' + response.json.results[0].geometry.location.lng + ' ' + array[0] + ' ' + array[1]);
var distanceBetween = distance(response.json.results[0].geometry.location.lat, response.json.results[0].geometry.location.lng, array[0], array[1]);
console.log(distanceBetween + " distance between spots");
var refList = db.ref("distances/"+array[2]);
console.log(snap[user].username + " snap username");
refList.push({
username: snap[user].username,
distance: Math.round(distanceBetween * 100) / 100
})
resolve();
})
.catch(err => { console.log(err); resolve();})
}
else {
resolve();
}
}).catch(err => console.log('error from catch ' + err)));
//console.log(typeof user + 'type of');
}
var p = Promise.all(promises);
console.log(JSON.stringify(p) + " promises logged");
res.status(200).end();
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
});
What is weird is, when I check the firebase functions logs, all of this appears to only run once...but I still think the subscription could be capturing the whole sorting process in some weird way while rapidly returning it. To be as clear as possible with what I think is going on - I think each stage of the sort is being captured in an (N(N + 1))/2...starting at 1 and going to roughly 30...and the sum of the sorting ends up being the length of my list (with 1-10 items repeated over and over again).
I updated to angularfire2 5.0 and angular 5.0...which took a little while, but ended up solving the problem:
this.distanceList = this.af.list('/distances/' + this.username,
ref => ref.orderByChild("distance").limitToFirst(50)).valueChanges();
In my HTML I used an async pipe, which solved the sorting problem:
...
<ion-item *ngFor="let z of (distanceList|async)" no-padding>
...
I am making a discord bot using js, but for some reason I can not get the bot to print an array properly via a for loop. The code is below
const Discord = require('discord.js');
const getJSON = require('get-json')
const BotToken = "tokenid";
const TwitchClientID = "?client_id=clientid";
const TwitchAPI = "https://api.twitch.tv/kraken/streams/";
const bot = new Discord.Client();
var channelNames = ["channel1", "channel2", "channel3"];
bot.login('botloginid');
// !Live command that itterates through channelNames array and prints whether stream is currently live or not
bot.on("message", function(message) {
if (message.content === "!Live") {
for (var i = 0; i < channelNames.length; i++) {
getJSON(TwitchAPI+channelNames[i]+TwitchClientID, function(error, response) {
if (response.stream == null) {
message.reply(channelNames[i] + ' is currently not live');
} else {
message.reply(channelNames[i] + ' is currently live');
}
});
}
}
});
If I put in a message.reply('i is ' + i); before getJSON, it prints out 0 1 2, which is correct, but if I put message.reply('i is ' + i); after getJSON, it prints out, i is 3, i is 3, i is 3. And because the array indexes are 0,1,2 , returning 3 makes the bot display 'undefined is live/not live' rather than the name of the channel. I've never had a problem with loops in the past and I cant understand why it would be changing to 3 under getJSON and not even iterating as the loop is definitely working.