Discord.js - Secretly getting user input - javascript

I am making a 2-player rock-paper-scissors game using Discord.js.
Sadly the Discord API is really slow and afaik doesn't provide any type of middleware. When someone chooses their shape, the other person sees the reaction (or chat message) for quite a while until the bot deletes it, therefore ruining the whole game.
The only way of secretly getting an input I could think of, was sending the user a message in private chat, to which he can react. But having to switch from the server to private chat and then back to the server just makes the game unplayable in my opinion. It's just too much work for the user, compared to simply clicking a reaction.
Another option would be sending a message in the chat, which only a specific user can see. It could say something like "1 = Scissors, 2 = Rock, 3 = Paper". (The mapping would be randomized for each player). The user then picks the corresponding reaction from the options 1, 2 and 3.
But it seems, Discord does not allow to send a message in chat, which only a specific user can see. Or is there a way?
And is there any way of secetly getting user-input without the user having to switch chats?
Does the API maybe provide any kind of middle-ware for messages or reactions which I have overlooked?

Discord does not allow to send a message in chat, which only a specific user can see. Or is there a way?
No, there isn't. Discord API doesn't allow you to specify users that can see a specific guild message.
And is there any way of secetly getting user-input without the user having to switch chats?
There definitely is!
You could use a fairly new feature, buttons. Below is an example code, you can use to base your game on. Things left to implement are:
Gamestate, e.g. who is on turn, who has how much points, etc.
Update gamestates (identify gamestate by id?) in the interactionCreate event's callback.
Showing the gamestate to the players, e.g. updating the original message.
Don't allow players to modify the gamestate of other playing pairs.
Let the user specify an opponent in !createGame command.
The actual game logic (determining who won, who gets a point, etc.)
That is all I can think of for now. Take those more of a suggestions than requirements. There is no boundary to ones creativity.
// ... Some object to store the gamestates in? ...
client.on("messageCreate", async (message) => {
// Don't reply to bots
if (message.author.bot) return;
// Basic command handler, just for showcasing the buttons
if (message.content.startsWith("!createGame")) {
// ... Probably some argument handling for the opponent (e.g. a mention) ...
// Create an action row component with buttons attached to it
const actionRow = new Discord.MessageActionRow()
.addComponents(
[["Rock", "🗿"], ["Paper", "🧻"], ["Scissors", "✂️"]].map((buttonProperties) => {
return new Discord.MessageButton()
.setStyle("PRIMARY")
.setLabel(buttonProperties[0])
.setEmoji(buttonProperties[1])
.setCustomId(`rpsgame_${buttonProperties[0].toLowerCase()}`);
})
);
// Send the game message/playground
message.channel.send({
content: "Rock, Paper and Scissors: The game!",
components: [actionRow]
});
}
});
To handle the button clicks, we use the interactionCreate event.
client.on("interactionCreate", (interaction) => {
// If the interaction is a button
if (interaction.isButton()) {
// If the button belongs to our game
if (interaction.customId.startsWith("rpsgame")) {
// Log what the user has selected to console, as a test
console.log(`A user '${interaction.member.user.tag}' selected ${interaction.component.emoji.name}.`);
// Don't forget to reply to an interaction,
// otherwise an error will be shown on discord.
interaction.update("Update GameState...");
}
}
});
No one will see what the other users have selected, unless you want them to.
Using discord.js ^13.0.1. If you are on v12, there is a nice package called discord-buttons.

Related

Discord.js Bot can't handle multiple buttons in the same channel (Version 13)

Is there a way to have a command with buttons be used by two people at the same time in the same channel?
I made an adventure system which is going well so far, but I found out that only one person can use it in a channel. If another person uses it in the same channel, the "DiscordAPIError: Unknown Interaction" error comes as soon as a button is used. I tried adding the message author's id to the custom ID of the buttons, but it still doesn't work for some reason and I am utterly confused.
From what I understand, I think it's unrelated to there being multiple button instances, but I can't think of any other reason. Here is the filter:
const filter = async (interaction) => {
if (interaction.user.id === message.author.id) return true;
await interaction.deferReply()
await interaction.editReply({ content: `**${interaction.user.username}**, You can't use this button!`, ephemeral: true})
return false
}
And the button click detector goes something like this:
GameCollect.on('collect', async ButtonInteraction => {
ButtonInteraction.deferUpdate()
// My code
})
I have tried:
Removing ButtonInteraction.deferUpdate() from all my click collectors.
Making the custom ids for the buttons have the message author's id at the end and making my code compatible with that change.
Smashing my head on my keyboard from confusion. Didn't work either... (satire)
If it's necessary, I can copy my code into a repl so that you can identify the problem easier, but I don't think the other portions would have any effect on this. I think it's just a piece of code/slight adjustment that I have to add for it to work with two people in the same channel.
EDIT: Alright, here is a source bin so that you can identify the problem better. Check the comments on the recent answer for some clarifications on my testing too. I will keep you updated if I find a solution.
DISCLAIMER: I still haven't found a way to make this work in over a month, and I am essentially just giving up on buttons. Thank you to everyone who helped though.
Try it:
Remove await interaction.deferReply() from filter
Create dynamic customIds like Math.random()
EDIT:
description: i had the same issue recently on my own bot, The problem is that when 2 messages that the have collectors(basically they're waiting for a interaction), if you call await interaction.deferReply() soon, it wil get messy, So here's the solution
First of all lets go for the filter function for checking the interaction, We need 2 things:
Checking the button id with the incoming interaction button id
Checking the user to be the real one
After all of these
delete deferReply from the filter func, Because when you didn't check the interactions, calling deferReply, will call both of messages or all of the messages that are waiting for interaction
add the deferReply to the function that is after filter
And don't remember to use random ids for components(Buttons, select menus and ...)
Instead of creating a collector on the text channel, you should create the collector on the actual message
const GameCollect = message.channel.createMessageComponentCollector({ filter, time: 60000 * 15 })
To
const GameCollect = <Message>.createMessageComponentCollector({ filter, time: 60000 * 15 })

Server Mute Music Bot? .setDeaf is not a function?

Well so I am trying to server mute my bot when it joins a VC but all I get is
TypeError: bot.setDeaf is not a function
I've tried the same with
let bot = message.guild.members.cache.get('ID');
But that didn't work either.
What am I doing wrong? I couldn't really find anything anywhere that would help me.
Here's the rest. Thanks in advance!
let bot = client.users.cache.get('ID');
bot.setDeaf(true);
Note 1. You can only call .setDeaf() from a VoiceState. Even though you can only have one VoiceState per user, the VoiceState is scoped to the instantiating GuildMember instance.
Note 2. You can access the VoiceState property via GuildMember.voice, and then call the relevant methods.
Note 3. Contrary to what others suggested, there is a fine difference between .setDeaf() and .setSelfDeaf(). Firstly, .setDeaf() server-deafens the member of that voice state. That means there will be a nice red icon next to that member's voice presence. . An entry to the audit log will also pop up, and you can specify the reason in .setDeaf().
On the other hand, .setSelfDeaf() can only be called on the bot's VoiceState. The library internally checks if the VoiceState user ID matches the client user ID. (see https://github.com/discordjs/discord.js/blob/master/src/structures/VoiceState.js#L192)
Self-deafening will not generate an audit log entry, and the icon next to the bot will be gray, not red.
So your code would be as follows:
//exit if the voice state property does not exist
if(!message.guild.me.voice) return message.channel.send("I'm not in a voice channel...");
//deafen
message.guild.me.voice.setDeaf(true, "Yeah...this is a deafening test.").then(() => {
message.channel.send("I have successfully server deafened myself.");
});
You can view the documentation for .setDeaf() here

Is there any way to create a "Free-write" section for a Chatbot?

Quite new here and have been using Dialogflow and rasa to try making a chatbot for user reports, Stuck at a certain segment where I want the user to "report" to the bot what it has done for the day, regardless of the content the bot will reply with a "Thanks for your time" response and saves the user's response, I can't seem to figure this out, as the bot will try to analyze the layer of texts the user sends and then goes to default fallback intent....
You will need FormAction for free write action (given you don't want any intent for it).
In the actions.py, add following code
class FormFreeText(FormAction):
def name(self):
return "form_free_text"
#staticmethod
def required_slots(tracker):
return ["free_text"]
def slot_mappings(self):
return {
"free_text": self.from_text()
}
def submit(self, dispatcher, tracker, domain):
return []
In domain.yml, add following lines
slots:
free_text:
type: unfeaturized
responses:
utter_ask_free_text:
- text: "Please enter the text for your report"
actions:
- form_free_text
In stories.md, add the following lines where ever you want user input.
- form_free_text
- form{'name':'form_free_text'}
- form{'name':null}
There are some naming convenstions like using utter_ask to ask any question using FormAction.
I hope this helps.
In DialogFlow your option is to use a fulfilment which allows to drive the conversation logic using events, for example:
user answers what's wrong or good about the day
webhook receives the payload with the answer and the associated intent (or better define an action)
webhook triggers an event (EVENT_DONE)
the event (EVENT_DONE) belongs to an intent (THANK_YOU) which sends a response to the user
In Rasa the options are to either train an intent (inform) with training data (you can still capture/recognise what the user talks about) or use an FormAction where user input is captured in a slot (even if you don't really need to use/store it)

How can I make a discord bot that will message user a specific code when he runs a command? Kind of like an account generator

So basically I'm trying to make a code for my discord.js bot that will message users with a steam key when they run a specific command. I came up with the idea when I thought of giving VIP members a special feature where they can get a steam key every day by just using a simple command so that my private messages don't get spammed. I will be providing the steam keys. The feature should message a unique steam key to each user who uses a command, and should not message the same key to different users. I've coded so many other commands but this one is really troubling me, I'm not able to figure out how I can create such a feature. Please help.
So this is basically for my discord server and should be coded in discord.js. I can create a code but the thing that troubles me is that I cannot stop the repetition of the keys...
Explanation
As Ricky Mo commented,
Maintain a pool of unsent keys and a table of users with the keys they got in a database.
This works because a user can receive a key from the unused stash, and once it's removed, it can't be given out again. If you want to allow only one key per user (or implement a cooldown) you can store users and their key.
A real database is a much better option than a JSON file, but your possible choices are too much to cover in one simple example.
Example Setup
keys.json:
{
"__UNUSED": [
"ABCD",
"1234",
"0000"
],
"userIDHere": "keyHere"
}
Inside command:
/* Async context needed for 'await' (meaning within an async function). */
const fs = require('fs');
const keys = require('./keys.json');
try {
// Ensure the user hasn't claimed a key already.
if (keys[message.author.id]) return await message.author.send(':x: You already claimed a key.');
// Ensure there's keys remaining.
if (keys.__UNUSED.length === 0) {
console.error('Out of keys.');
return await message.author.send(':x: Sorry, no keys are available to claim right now.');
}
// Grab the first key (no need to randomize).
const key = keys.__UNUSED[0];
// Send the user the key, then remove it from the pool, catch any errors.
await message.author.send(`Here's your key: ||${key}||`);
keys.__UNUSED.splice(1);
keys[message.author.id] = key;
fs.writeFileSync('./keys.json', JSON.stringify(keys));
} catch(err) {
console.error(err);
}
...I thought of giving VIP members a special feature where they can get a [S]team key every day...
If you want to incorporate this, you could set an interval or create a cron job to remove the user from the JSON/database every 24 hours.

Streaming data from the Server to Client with Meteor:

I'm developing a text based adventure game with Meteor and I'm running into an issue with how to handle certain elements. Namely, how to emit data from the Server to the Client without any input from the Client.
The idea is that when a player is engaged in combat with a monster, the combat damage and updating the Player and Monster objects will be occurring in a loop on the server. When the player takes damage it should accordingly update the client UI. Is something like this possible with Publish / Subscribe?
I basically want something that sits and listens for events from the server to update the combat log accordingly.
In pseudo-code, this is something along the lines of what I'm looking for:
// Client Side:
Meteor.call("runCommand", "attack monster");
// Server Side
Meteor.methods({
runCommand: function(input) {
// Take input, run the loop to begin combat,
// whenever the user takes damage update the
// client UI and output a line saying how much
// damage the player just received and by who
}
});
I understand that you can publish a collection to the client, but that's not really as specific of a function I'm looking for, I don't want to publish the entire Player object to the client, I just want to tell the client to write a line to a textbox saying something like "You were hit for 12 damage by a monster!".
I was hoping there was a function similar to SocketIO where I could, if I wanted to, just emit an event to the client telling it to update the UI. I think I can use SocketIO for this if I need to, but people seemed to be adamant that something like this was doable with Meteor entirely without SocketIO, I just don't really understand how.
The only outs I see to this scenario are: writing all of the game logic client-side which feels like a bad idea, writing all of the combat logs to a collection which seems extremely excessive (but maybe it's not?), or using some sort of SocketIO type-tool to just emit messages to the client to tell it to write a new line to the text box.
Using Meteor, create a combat log collection seem to be the simplest option you have.
You can only listen on added event and then clear the collection when the combat is over.
It should be something like this :
var cursor = Combat_Log.find();
var handleCombatLog = cursor.observe({
added: function (tmp)
{
// do your stuff
}
});
I ask a similar question here, hope this will help ^^
Here's how I did it without a collection. I think you are right to be concerned about creating one. That would not be a good idea. First install Streamy.
https://atmospherejs.com/yuukan/streamy
Then on the server
//find active sockets for the user by id
var sockets = Streamy.socketsForUsers(USER_ID_HERE)._sockets
if (!Array.isArray(sockets) || !sockets.length) {
//user is not logged in
} else {
//send event to all open browser windows for the user
sockets.forEach((socket) => {
Streamy.emit('ddpEvent', { yourKey:yourValue }, socket);
})
}
Then in the client, respond to it like this:
Streamy.on('ddpEvent', function(data) {
console.log("data is ", data); //prints out {yourKey:yourValue}
});

Categories

Resources