Discord %sticky command - javascript

I'm currently trying to create a Discord bot that sends a message when a user with a specified role sends the command %sticky This is a test
I want it to always be the first message in the channel and every time another user types in the channel the bot deletes its last message and posts again. I haven't had any luck online yet even finding a bot that already does this functionality, or where to even start. Here's what I kind of have so far
var lastStickyMessage;
client.on('message', message => {
if (lastStickyMessage != null) {
message.channel.fetchMessage(lastStickyMessage)
.then(retrievedMessage => retrievedMessage.delete());
}
message.reply("This is a Sticky Message").then(sent => {
let lastStickyMessage = sent.id;
}
});

There are several errors with your variable management: One one hand you create a new let with the same name. Since a let is a scoped variable, the lastStickyMessage will have a different value inside the sent callback than it has outside of it, since those are two different variables (read more on this here).
Apart from that you should save the last sent ID in a file or somewhere since the var will be reset once you restart your bot (the built in fs module could help you with that, you can find the documentation here).
One last thing: If you initialize a variable without a value it is not null but undefined. If you only check using == it will still evaluate to true (means null == undefined) but if you compare using ===, it will evaluate to false (null !== undefined). In your case this is not really a problem but this might be good to know for other cases.

Related

Using discord.js, how would I check if a user has sent a message two times in a row?

For a specific channel in my server, I want to make it so that a user can't send two messages in a row.
Here's the code I got so far:
client.on('message', message => {
if(message.guild.id != '123478291364192834') return;
if(message.channel.id != '432874912364123984') return;
let messageauthor = ''
message.channel.messages.fetch({limit:2}).then(messages=>{
messages
.forEach(message=>{
messageauthor = message.author.id
})
})
if(messageauthor == message.author.id){
message.delete()
}
})
The idea was that it'd fetch two messages in a channel and if the message author of the second message is fetched is the same as the message author, it'd delete the message. But it doesn't do that, does anyone know why or have any solutions to the problem?
There are several problems with your code as-is.
Problem
First of all, you're checking if messageauthor is equal to message.author.id outside of your .then(). The code inside a .then() has its execution delayed because it executes asynchronously. In other words, it takes some time to fetch the messages in the channel, which is why you can only access messages in your .then(). Because the contents of your .then() execute after the rest of your code, including the code that proceeds it, you are checking if messageauthor is equal to message.author.id before the value of messageauthor is even set. Your if statement will always be checking if the empty string '' is equal to the ID of the message's author, which is always false, so the message will never be deleted.
Second, inside your .then() you are looping through the two messages you've fetched and are setting messageauthor to the ID of the message's author. But since you're doing this twice, since you fetched two total messages, that means that messageauthor is only set to the author ID of the second message. So why loop through both messages, when you can just immediately grab the author ID from the last message?
Finally, you're only checking if the author of the second message matches the ID of the current message's author. How that behaves depends on whether or not the current message is fetched along with the last message. If the current message is included in messages, then the second message literally is the current message, so that will delete every single message that anyone ever sends. If the current message isn't included in messages, then why are you fetching two messages with your .fetch()? From your question, it seems you do not want people to send two messages in a row. That would mean you only have to fetch 1 previous message and use just the message you fetched in your comparison. I will attempt to account for both of these possibilities below with two separate answers, as I am not sure whether the current message will be included.
Solution
Here's how I would suggest you revise your code, assuming the current message is included in the fetched messages (I'm assuming this will not be the case):
client.on('message', message => {
if(message.guild.id != '123478291364192834') return;
if(message.channel.id != '432874912364123984') return;
message.channel.messages.fetch({limit:2}).then(messages=>{
let firstauthor = messages.first().author.id;
if(firstauthor == message.author.id){
console.log("Deleting message because 2 messages in a row were sent");
message.delete();
}
});
})
Here's what I would do if this current message is not included in the fetched messages (which I assume will be the case, so this should work properly):
client.on('message', message => {
if(message.guild.id != '123478291364192834') return;
if(message.channel.id != '432874912364123984') return;
message.channel.messages.fetch({limit:1}).then(msg=>{
let firstauthor = msg.author.id;
if(firstauthor == message.author.id){
console.log("Deleting message because 2 messages in a row were sent");
message.delete();
}
});
});
In both of these fixes, what I've done is first and foremost I've moved the if statement into the end of the .then(). This ensures that the condition is only checked after the messages have been fetched. Second, I removed the forEach loop and replaced it with simple references to the first, or only, item in the fetched message collection. Third, I made sure it was the first message, and not the second message, that would have its author's ID compared to the current message author's ID. And finally, I added a simple console.log to help you debug the issue. If the console.log never occurs but you know it is supposed to, that means something is wrong in the code.
Feel free to try either solution, or both, out and find which one works for you. I have not tested either thoroughly, so if you find an issue let me know and I will fix it. This should give you a general idea of what the issue is in your code nevertheless, so you should hopefully be able to fix the issue on your own even if these solutions do not work. I am guessing the second of the above solutions will work best.

I cant get an item from an array for my Discord bot to use to take action

I am trying to program a bot for a server, and I am currently working on making a 'Mute' command. My idea was to make it so when the command is called, the bot must first check if the person has the role which allows them to mute other members, if that condition is met, then the bot would take the second argument, aka the Discord id of the member which must be muted, and give them a role which inhibits them from speaking in the server.
For some reason when I was testing the code, I wasn't able to get past the bot checking whether the second argument was a valid ID for a member of the server.
Here is the mute command code:
if (message.member.roles.cache.has('765334017011613717')) {
console.log('Oh oh');
const person = message.guild.member(
message.mentions.users.first() || message.guild.members.get(args[1])
);
if (!person) {
console.log('Oh oh 2');
}
} else message.channel.send('You do not have permission to use this command.');
Note: The console.log() functions were added so I could see where the problem was happening when running the code.
There are two things that you might do in order to make this work with the first being more of a suggestion.
First, ideally you always want to check if the role exists and return if it doesn't. This prevents you from having to put your entire logic to mute the person into the if statement. You do that like this:
if (!message.member.roles.cache.has('your role ID')) {
return message.channel.send('You do not have permission to use this command.');
}
The actual problem in your code stems from your way of finding the member. The right way to do that is, either checking the first mention of a member or taking a provided ID from your first argument. Your first argument being not args[1] but args[0]. If the ID is being used you need to use the cache property. Source
let muteMember = message.mentions.members.first() || message.guild.members.cache.get(args[0]);
You should then check if a member has actually been found and return if not.
if (!muteMember) {
return message.channel.send("That member doesn't exist!")
}
Note: You already have this in your code but I wanted to include it for completeness.
After that you can continue with your code to mute someone.

discord.js user specific snowflake id commands

so im trying to get my bot to detect a user id to allow a command so a command for only one user and im having a bit of trouble understanding what i am doing wrong
run(message) {
//member id 184191493919997952
if(message.member.id.find('id', '184191493919997952')){
return message.say('True')
}else{
return message.say('False')
}
}}
so ive tried to set it up to check whoever used the command and if their snowflake id isnt equal to the one listed then it should return false but i keep getting a TypeError am i missing something?
ID is a property of User (which is message.author).
You can directly use "==" to check if it equals to something.
run(message) {
if (message.author.id == "184191493919997952") {
message.reply("true")
} else {
message.reply("false")
};
};
https://discord.js.org/#/docs/main/stable/class/User?scrollTo=id
Also, there is no method called "say" of message.
You can check available methods here (for example, message.reply()):
https://discord.js.org/#/docs/main/stable/class/Message

How Exactly Does Discord.JS .awaitMessages Work?

I recently heard of .awaitMessages in Discord.JS and I did search it up and look at some tutorials, but I'm still not really sure of how it exactly works and how to use it properly. I would really appreciate if someone could tell me how it works and how to use it. Thanks a lot!
I've got you covered my man/woman, I wrote some example code and commented on it like crazy explaining everything I put down for you :)
client.on('message', message => {
// This just converts all messages to complete lowercase for the bot to interpret.
const command = message.content.toLowerCase();
// This variable simply stores the User ID of the person who sent the message.
const messageAuthor = message.author.id;
if (command == 'what is your name?') {
// This is the filter we will use for the Message Collector we're going to use A.K.A ".awaitMessages".
const messageFilter = message.author.id == messageAuthor;
/* This is the command you asked about in all its glory. The first parameter you give it is the filter to use (although a filter isn't required if I recall correctly.).
The next parameter is "max", this purely dictates how many messages (applying to the filter if applicable) for the collector to collect before ending (unless of course the timer runs out, which we'll touch on next).
Second to last parameter is "time", this is, like max, pretty straightforward and dictates the amount of time the collector attempts to collect messages before ending (as stated above, if the collector-
reaches the max for collected messages before the timer runs out it will end prematurely, however if for whatever reason the collector doesn't receive the max amount of collected messages, it will continue to attempt collect messages-
until the timer runs out.)
Now onto the last parameter, "errors". This parameter basically tells the collector to treat the timer running out as if it was an error, that way you can then reference said error in the ".catch" method you'll see below and give the bot-
instructions on what to do if the time does end up running out.
*/
message.channel.awaitMessages(messageFilter, { max: 1, time: 10000, errors: [time] })
.then(collected => {
// Checks to see if the message we collected is a number, and if so, responds with the message in the command below.
if (isNaN(collected) == false) {
// The response for if the collected message IS a number.
message.channel.send(`${collected} is definitely not your name!`);
}
// This runs as long as the message we collected is NOT a number.
else {
// The response if the collected message is NOT a number.
message.channel.send(`${collected} is an awesome name!`);
}
})
.catch(collected => message.channel.send('You never gave me your name ):'));
// ^ This is the ".catch" method where you'll give the instructions for what to do if the timer runs out, in this case I have it just reply to the user to tell them that they never gave me their name.
}
});

How to throw a custom message using Dialogflow after three times of fallback

I am developing a chatbot using Dialogflow, I would like to throw a message to user when the chatbot doesn't understand the user input for three times in a row and for the forth time respond with a custom message (not the one of the options declared on the dialogflow interface)
One idea that I have is to make a counter within the input unknown action like this:
var counter = 1;
// The default fallback intent has been matched, try to recover (https://dialogflow.com/docs/intents#fallback_intents)
'input.unknown': () => {
// Use the Actions on Google lib to respond to Google requests; for other requests use JSON
if (requestSource === googleAssistantRequest) {
sendGoogleResponse('I\'m having trouble, can you try that again?'); // Send simple response to user
} else {
if (counter == 3) {
counter = 1;
sendResponse('Custom message');
} else {
counter++;
sendResponse('I\'m having trouble, can you try that again?'); // Send simple response to user
}
}
},
This would work, but idk if this will work for multiple user at the same time, I was thinking to create a storage for storing requests attached by a unique id and have a different counter for each request!
Do you have any better idea of achieving such thing in Dialogflow?
This will not work the way you've designed it. Not quite for the reason you think, but close.
You don't show the rest of your code (that's ok), but the counter variable is probably in a function that gets called each time it processes a message. When that function is finished, the counter variable goes out of scope - it is lost. Having multiple calls at the same time won't really be an issue since each call gets a different scope (I'm glossing over some technical details, but this should be good enough).
One solution is that you could store the variable in a global context - but then you do have the issue of multiple users ending up with the same counter. That is very very bad.
Your solution about keeping a counter in a database, keyed against the user, does make sense. But for this need, it is overkill. It is useful for saving data between conversations, but there are better ways to save information during the same conversation.
The easiest solution would be to use a Dialogflow Context. Contexts let you save state in between calls to your webhook fulfillment during the same conversation and for a specific number of messages received from the user (the lifespan).
In this case, it would be best if you created a context named something like unknown_counter with a lifespan of 1. In the parameters, you might set val to 1.
The lifespan of 1 would mean that you'll only see this context the next time your webhook is called. If they handle it through some other Intent (ie - you understood them), then the context would just vanish after your fulfillment runs.
But if your input.unknown handler is called again, then you would see the context was there and what the value is. If it doesn't meet the threshold, send the context again (with a lifespan of 1 again), but with the value being incremented by 1. If it did meet the threshold - you'd reply with some other answer and close the connection.
By "send the context", I mean that the context would be included as part of the reply. So instead of sending just a string to sendGoogleResponse() or sendResponse() you would send an object that included a speech property and an outputContexts property. Something like this:
var outputContexts = [
{
name: 'unknown_counter',
lifespan: 1,
parameters: {
'val': counterValue,
}
}
];
sendResponse({
speech: "I'm confused. What did you say?",
outputContexts: outputContexts
});

Categories

Resources