Open AndrewYatzkan opened 6 years ago
Does require
work inside the >object
?
> object makeJoke javascript
const main = require('main.js');
console.log(main.test);
< object
just returns [ERR: Error when executing JavaScript object: Cannot find module 'main.js']
Ah - that sounds familiar so I went searching and found issue #47 where I looked into this before.
RiveScript loads the >object
by using eval()
, so the object runs from the context of the rivescript/lang/javascript
package, and import paths had to be relative to there... like doing require "../../local_module"
There might be an elegant workaround by messing with Node's import paths for where it searches for modules, but it would take more experimenting. You can pass a scope in to the reply function (like this
) and an object can access local variables on that same this
as a way to make some dependencies available to it as a possible workaround.
I'm probably missing something, but in order to use the reply function you must use bot.reply('local-user', 'message')
, but bot
isn't defined in the rive file
Object macros are allowed to use Promises if you invoke the bot's reply with the replyAsync()
function instead of reply()
. Here's an example. You use the resolve()
function of the promise to return reply text to be used in place of the <call>
tag in the response.
Source code of an object macro (whether the body of a setSubroutine()
or defined in RiveScript in an > object
):
return new Promise(function(resolve, reject) {
// do something async
setTimeout(function() {
resolve("here is the reply!");
}, 1000);
// reject("an error reply instead on error");
});
With this RiveScript code:
+ test
- The answer is: <call>objectname</call>
the bot should eventually reply with "The answer is: here is the reply!"
The application running the bot would have to use replyAsync() for it to support the promise; using reply() would have the <call>
tag just substitute an error message (it would still execute the code, but it has no way to wait for the resolve()
answer, so the <call>
tag gets the error message as a placeholder instead).
var bot = new RiveScript();
bot.loadFile("test.rive");
// says something like "The answer is: [ERR: can't run async object: use replyAsync instead]"
var reply = bot.reply("user", "test");
// works as intended
bot.replyAsync("user", "test").then(function(reply) {
console.log("Bot>", reply);
});
Thanks for the help, and I'm sorry for taking your time, but if you can't tell I'm pretty new at this, and I'm sort of lost. I tried changing reply to replyAsync in return e.message.channel.sendMessage(talkback.replyAsync("local-user", triggr))
, but it just returns [object Object]. I can send you the code if that would help, but if that takes too much time I completely understand
replyAsync()
returns an object of type Promise
, which isn't directly useful as a return value. You just have to invert your function calls.
talkback.replyAsync("local-user", triggr).then(function(reply) {
e.message.channel.sendMessage(reply);
});
Async in JavaScript allows the interpreter to do multiple things at the same time, by letting one task defer itself and come back with an answer "in the future." The reply()
function is synchronous, which means that while reply()
is busy searching the bot for a reply, your JavaScript app isn't able to handle any other tasks at the same time, for example it can't answer other people while it's busy answering one person. The other incoming messages have to wait their turn. With replyAsync()
it allows the JavaScript engine some time to do other tasks while it waits for a reply. When the reply is ready, the .then()
handler can be called and you can send that reply to the user who requested it.
A lot of programming languages are synchronous by default (examples: Python, Perl, C/C++), if you have a function that has to do work that takes 5 seconds before coming up with an answer, your entire program "blocks" on that function for the whole 5 seconds and it can't do anything else during that time. Languages can work around it by using threads or multiple processes, but JavaScript is single-threaded and can't do true multitasking. Instead it has its async model where a function can voluntarily give control back to the interpreter to do other things while it waits. This is most often done with I/O bound tasks, like waiting to read from a file on disk, or waiting for a network response to come back, things that can take a long time from a CPU perspective and which would just waste the program's time if it needed to wait around for it.
Now the bot runs as it originally did but with replyAsync instead of reply, but
+ test
- <call>makeJoke</call>
> object makeJoke javascript
return new Promise(function(resolve, reject) {
// do something async
setTimeout(function() {
resolve("here is the reply!");
}, 1000);
// reject("an error reply instead on error");
});
< object
just returns [object Promise]
It might need to use rs.Promise
instead of Promise
... when it was first implemented Promises weren't widely supported so rs.Promise
was a polyfill, but, the code might be expecting the polyfill. :cry:
That did the trick, but now how can I use a variable from an external javascript file in the resolve()?
@AndrewYatzkan was this solved?
@hisRoyalty @AndrewYatzkan
A couple ideas: if trying to import a module with require() from inside a > object
declaration in RiveScript, the import path may need to be modified because the macro was evaluated from the context of the rivescript/lang/javascript module so it needs more "../../" path components in front to get back to your app's directory. This is mainly only an issue when using the > object
syntax in your RiveScript file.
Alternatively you can declare the object macro from your JavaScript app using setSubroutine() and then normal scoping rules apply and it can access any variables in scope:
const RiveScript = require("rivescript"),
fetch = require("node-fetch"); // <- module you want to use
let bot = new RiveScript();
bot.setSubroutine("makeJoke", function(rs, args) {
const url =
"http://api.icndb.com/jokes/random";
fetch(url) // <-- use it here, it's in scope
.then(response => {
response.json().then(json => {
var joke = `${json.value.joke}`
talkback.setVariable('joke', joke)
});
})
.catch(error => {
console.log(error);
});
}
});
As a third option, the scope
parameter to reply() allows you to pass an object that you want "this" to refer to from the context of your object macro. You can use this to smuggle variables in or allow the >object
syntax to access properties and functions belonging to your program. The scope example shows this off: https://github.com/aichaos/rivescript-js/tree/master/eg/scope
I think something along these lines might work if you get clever with scope
:
// In your bot program
let scope = {
"fetch": require("node-fetch")
}
// Pass that scope in when you call reply()
let reply = await bot.reply(user, message, scope);
// In your .rive sources
> object getJoke javascript
let fetch = this.fetch;
return fetch('some url').then(res => res.json()) // or w/e
< object
Hello! I'm trying to use a joke API to have the bot send a joke when the user asks for one, but I'm running into a problem. This is the code I use to get the joke, then set the joke as a variable for the bot.
I'm doing this in an external javascript file and not as a rivescript object because I need to use
talkback.setVariable('joke', joke)
to set the variable, and talkback isn't defined in the rivescript file.I've tried many methods but I haven't found out how I can use the same function, or variable, across both the files. I've spent a long time on this, but I'm assuming it's a simple tweak that will make it work. Thanks!