Hubot nesting commands - javascript

I want to create a tree-style question and answer bot with hubot doing support services and I haven't been able to figure out how. I wanted Hubot to ask a question upon someone entering the room (with robot.enter) though that doesn't work with Rocket.Chat, I've found a workaround. But if I want to ask a question and wait for a user to reply to save their reply and ask them another question, how would I go about doing this?
I tried nesting even a res.send and it wouldn't allow me, giving me an index error on CoffeeScript

If you want something prebuilt, there are a couple framework scripts that provide this capability:
https://github.com/lmarkus/hubot-conversation
https://www.npmjs.com/package/hubot-dynamic-conversation
hubot-conversation is a little more JavaScripty (and ironically, a little more dynamic), whereas hubot-dynamic-conversation centers around you building a JSON model of the conversation flow.
If you don't like either of those options, you can always implement your own flow using a mixture of robot.listen to dynamically match messages and the brain to track state.
Example (that I haven't actually tested, but should give the right idea):
module.exports = (robot) ->
robot.respond /hello$/, (res) ->
res.reply 'Why hello there! How are you today?'
# Record that we are mid-conversation
convs = robot.brain.get('conversations')
convs = convs.push(message.user.name)
robot.brain.set('conversations', convs)
robot.listen(
# If we are mid-conversation, respond to whatever they say next
(message) -> message.user.name in robot.brain.get('conversations')
(response) ->
response.reply 'I hope you have a great rest of the day!'
# Record that we are done conversing
convs = robot.brain.get('conversations')
convs = convs.splice(convs.indexOf(message.user.name), 1)
robot.brain.set('conversations', convs)
)

According to https://github.com/github/hubot/blob/master/docs/scripting.md
You can just use:
robot.enter (res) ->
res.send res.random enterReplies

don't know whether still have solution on this, since TS mention robot.enter in rocketchat does not work.

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 })

Embedded data in Qualtrics showing up in the 'Data & Analysis' tab, but is not showing up for participants

So I have a bit of JS in my Qualtrics survey that takes the response to a question, and if that response is not empty, it will embed the participant’s response (to be called later in the survey). If they don’t enter anything, I have made it print a string. I have done this with the following:
Qualtrics.SurveyEngine.addOnUnload(function()
{
/*Place your JavaScript here to run when the page is unloaded*/
var tboxID= "QR~"+this.questionId;
var hometownStr = document.getElementById(tboxID).value;
Qualtrics.SurveyEngine.setEmbeddedData('hometownStr',hometownStr);
if(!hometownStr || 0 === hometownStr.length)
{
Qualtrics.SurveyEngine.setEmbeddedData('hometownStr', "your hometown");
}
});
After the block this question is in I have embedded the data.
So, let's say I am asked the question (in which the JS is embedded): What is your hometown? I either answer that my hometown is (for example) "Paisley", or I leave it blank. If I don't answer the question, the next question will say This is a person from your hometown. If I
do answer the question, it should print: This is a person from Paisley.
However, the latter example does not print "Paisley" (it prints nothing). FYI, the former example works fine.
The strange thing is that, though "Paisley" is not printed in the survey, it comes out in the final data (found in the Data & Analysis tab).
Note: I am calling the embedded data by ${e://Field/hometownStr}.
Does anyone know why this might be happening? Thanks in advance for any help; please tell me I am stupid and overlooking something major, and this isn't a bug in Qualtrics...
FYI, this code has worked in the past, so I don't think it's an issue with the code. I refactored some of the questions and blocks and it just stopped working.
Move the survey flow embedded data block BEFORE the block where the JS sets it.
EDIT:
I noticed you are using addOnUnload. Use addOnPageSubmit instead.

Sending messages across channels with Discord.js, yields 'undefined' error

I'm looking to create a 'say' command that allows me to directly message through the bot to a specific (or any, perhaps) channel. For beginner, and general testing purposes, my goal is to be able to use a standard if(commandIs("command", message) to directly message a channel in my test server, eventually evolving to all channels on a server.
Through my research I've stumbled upon the:
var channel = client.servers.get("name", "My Server").defaultChannel;
client.sendMessage(channel, "Hello");
code, which is exactly what I'm looking to do as a base since I can swap out the .get("name", "My server") for the actual channel ID, but doing this through a say-like command sets the var as a Channel class in my code, which doesn't support .sendMessage()
My current command code looks like:
if(commandIs("speak", message)){
var chat = client.channels.get(18numberID);
if(args.length === 1){
message.channel.sendMessage('You did not say anything. Usage: `a~speak [message]`')
} else {
chat.sendMessage(args.join(" ").substring(8));
-but this brings up an undefinederror on the .sendMessage(), which I figured it would. I've tried message.chat.sendMessage() and every other possible variation I could, even going to the bare two lines of code to test at on.ready(), but that continued to give me the same error. I've looked for a way around the Channel class created once the ID is found and that led me to the TextChannel and GuildChannel extensions, but I'm pretty sure there's an easier way around it considering all the code (even a couple examples here) do not contain all that extra information. I feel like I'm looking over something, or possibly complicating the code more than needed, but I'm not sure.
Any ideas or help would be appreciated.
Edit: It seems I was right and I looked over a few key points, specifically the channelID not having quotes around it to be a string. Tried the command alone and everything went great; tweaked the main code and it all worked out.
First of all, I just have to outline a few things that no longer work in the Discord.JS library (Thanks for the heads-up Ty Q.!)
First of all, .sendMessage() has been deprecated, and will more than likely not work at all anymore / soon. The working equivalent of this is .send() - which is simpler, and is an easy change to make.
Second: .defaultChannel is unfortunately no longer a thing. We will have to be more specific when we want to send a message.
I'm not great at talking about these kinds of issues, so if anyone else wants to pick it up, please do.
Alright, let's let your command be set out like this:a~speak [ChannelID] <Message>
Let's say we use this command: a~speak 12345689 I has a bucket
What we want to do is find out whether we can find the channel with the ID, and if we can, send the message to it.
To do that, I think we should see if we can use the client.channels.find("id", ID) function.
Let's Start by Setting Up Our Command
if(commandIs("speak", message)){
args = args.shift();
/* ["a~speak", "CHANNELID", "I", "has", "a", "bucket"]
BECOMES
["123456789", "I", "has", "a", "bucket"]
args [0] [1] [2] [3] [4]*/
let chan = client.channels.find("id", args[0]); // Find the channel ID "123456789"
if(chan) { // Check if that channel exists
chan.send(args.shift().join(" "));
} else {
message.channel.send(args.join(" "));
}
}
Let's Follow That Command
If we type in a~speak 12345689 I has a bucket, our bot will look for a channel that it has access to with the id "123456789".
If your bot found the channel with the id "123456789":
chan.send(args.shift().join(" "));
We defined earlier that chan = args[0] - which would be "123456789", and we don't want to send that in the message, so we use args.shift() - this removes the first argument from the rest, leaving us with ["I", "has", "a", "bucket"].
After that, we use .join(" ") - this connects ["I", "has", "a", "bucket"] with spaces to form I has a bucket.
We then simply send that message to the channel we defined, with chan.send().
If your bot did not find the channel with the id "12456789"
message.channel.send(args.join(" "));
This one's a bit different in a few ways, first of all, we forget about the chan variable, because we couldn't find that channel. If we know this, we can deduce that the user may have instead said something like a~speak I has a bucket - without the ID altogether.
Knowing that, we (unlike before), don't want to args.shift() - we would remove the "I" bit! Instead, we just want to args.join(" ") to once again join the words together into a sentence, then send it away with message.channel.send() - which just sends it in the server it got the message from.
If you need any more help, just comment with your Discord name, and I'll be happy to give you a little push in the right direction.
Sources of Information:
Some good places to look for help on these kinds of topics (Discord.JS), are the Official Discord.JS Server and AnIdiotsGuide. Both of which will be able to solve your problems.

Node.JS req.body String Too Long?

I am once again asking a probably beginners question, but I have looked around quite a bit, and was unable to find the answer I need. I have created a Node.JS server pulling data for a Game State Integration project I am working on, and I need to request certain data from the game.
Basically, what is happening in the code is I am setting the variable newKillStatus to a certain part of the player_state file from which I am reading. Before I talk a bit further, here is the code I am using: (If you need more, please ask)
fs.readFile('player_state', 'utf8', function(err, killStatus) {
var player = 'player' in req.body ? req.body.player : null;
if (player && player.state !== killStatus) {
var newKillStatus = req.body.player.state.round_kills;
if (!newKillStatus) {
newKillStatus = '';
}
fs.writeFile('player_state', newKillStatus);
console.log(newKillStatus);
}
});
So as you can see I am trying to set newKillStatus to req.body.state.round_kills; which, from what I can understand is too long, because when I try to start the server, it gives me this error:
cannot read property 'round_kills' of undefined
But If I get rid of the round_kills part it gives me round_kills, but it also gives me a whole bunch of other statistics that I don't want to set the variable to.
So basically my only question is how can I set var newKillStatus to req.body.player.state.round_kills without an error.
Thank you for helping!
EDIT:
I have tried all the solutions and multiple combinations of strings, is it possible that the underscore is creating an error?
After several attempts to find the answer myself, I have found it. I am actually quite surprised it took me so long to spot this. The probably was actually above what I though was causing it, and what the error said was causing it. The problem was actually in var player = 'player' in req.body ? req.body.player : null; I needed to change req.body.player to req.body.player.state do the code below would know what I referencing.

MEAN / AngularJS app check if object already posted

I have thig angularJS frontend and I use express, node and mongo on the backend.
My situation looks like:
//my data to push on server
$scope.things = [{title:"title", other proprieties}, {title:"title", other proprieties}, {title:"title", other proprieties}]
$scope.update = function() {
$scope.things.forEach(function(t) {
Thing.create({
title: t.title,
//other values here
}, function() {
console.log('Thing added');
})
})
};
//where Thing.create its just an $http.post factory
The HTML part looks like:
//html part
<button ng-click="update()">Update Thing</button>
Then on the same page the user has the ability to change the $scope.things and my problem is that when I call update() again all the things are posted twice because literally thats what I'm doing.
Can someone explain me how to check if the 'thing' its already posted to the server just to update the values ($http.put) and if its not posted on server to $http.post.
Or maybe its other way to do this?
I see a few decisions to be made:
1) Should you send the request after the user clicks the "Update" button (like you're currently doing)? Or should you send the request when the user changes the Thing (using ngChange)?
2) If going with the button approach for (1), should you send a request for each Thing (like you're currently doing), or should you first check to see if the Thing has been updated/newly created on the front end.
3) How can you deal with the fact that some Thing's are newly created and others are simply updated? Multiple routes? If so, then how do you know which route to send the request to? Same route? How?
1
To me, the upside of using the "Update" button seems to be that it's clear to the user how it works. By clicking "Update" (and maybe seeing a flash message afterwards), the user knows (and gets visual feedback) that the Thing's have been updated.
The cost to using the "Update" button is that there might be unnecessary requests being made. Network communication is slow, so if you have a lot of Thing's, having a request being made for each Thing could be notably slow.
Ultimately, this seems to be a UX vs. speed decision to me. It depends on the situation and goals, but personally I'd lean towards the "Update" button.
2
The trade-off here seems to be between code simplicity and performance. The simpler solution would just be to make a request for each Thing regardless of whether it has been updated/newly created (for the Thing's that previously existed and haven't changed, no harm will be done - they simply won't get changed).
The more complex but more performant approach would be to keep track of whether or not a Thing has been updated/newly created. You could add a flag called dirty to Thing's to keep track of this.
When a user clicks to create a new Thing, the new Thing would be given a flag of dirty: true.
When you query to get all things from the database, they all should have dirty: false (whether or not you want to store the dirty property on the database or simply append it on the server/front end is up to you).
When a user changes an existing Thing, the dirty property would be set to true.
Then, using the dirty property you could only make requests for the Thing's that are dirty:
$scope.things.forEach(function(thing) {
if (thing.dirty) {
// make request
}
});
The right solution depends on the specifics of your situation, but I tend to err on the side of code simplicity over performance.
3
If you're using Mongoose, the default behavior is to add an _id field to created documents (it's also the default behavior as MongoDB itself as well). So if you haven't overridden this default behavior, and if you aren't explicitly preventing this _id field from being sent back to the client, it should exist for Thing's that have been previously created, thus allow you to distinguish them from newly created Thing's (because newly created Thing's won't have the _id field).
With this, you can conditionally call create or update like so:
$scope.things.forEach(function(thing) {
if (thing._id) {
Thing.update(thing._id, thing);
}
else {
Thing.create(thing);
}
});
Alternatively, you could use a single route that performs "create or update" for you. You can do this by setting { upsert: true } in your update call.
In general, upsert will check to see if a document matches the query criteria... if there's a match, it updates it, if not, it creates it. In your situation, you could probably use upsert in the context of Mongoose's findByIdAndUpdate like so:
Thing.findByIdAndUpdate(id, newThing, { upsert: true }, function(err, doc) {
...
});
See this SO post.
#Adam Zemer neatly addressed concerns I raised in a comment, however I disagree on some points.
Firstly, to answer the question of having an update button or not, you have to ask yourself. Is there any reason why the user would like to discard his changes and not save the work he did. If the answer is no, then it is clear to me that the update should not be place and here is why.
To avoid your user from loosing his work you would need to add confirmations if he attempts to change the page, or close his browser, etc. On the other if everything is continuously saved he has the peace of mind that his work is always saved and you dont have to implement anything to prevent him from loosing his work.
You reduce his workload, one less click for a task may seem insignificant but he might click it many time be sure to have his work save. Also, if its a recurrent tasks it will definitely improve his experience.
Performance wise and code readability wise, you do small requests and do not have to implement any complicated logic to do so. Simple ng-change on inputs.
To make it clear to him that his work is continuously save you can simply say somewhere all your changes are saved and change this to saving changes... when you make a request. For exemple uses, look at office online or google docs.
Then all you would have to do is use the upsert parameter on your mongoDB query to be able to create and update your things with a single request. Here is how your controller would look.
$scope.update = function(changedThing) { // Using the ng-change you send the thing itself in parammeter
var $scope.saving = true; // To display the saving... message
Thing.update({ // This service call your method that update with upsert
title: changedThing.title,
//other values here
}).then( // If you made an http request, I suppose it returns a promise.
function success() {
$scope.saving = false;
console.log('Thing added');
},
function error() {
//handle errors
})
};

Categories

Resources