Skip to content

Commit

Permalink
Merge pull request #207 from matteoredaelli/master
Browse files Browse the repository at this point in the history
json-server-async: a merge of json-reply and async-reply examples
  • Loading branch information
kirsle authored Oct 2, 2021
2 parents 32acde1 + 2d21a4e commit 03b75b2
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 0 deletions.
77 changes: 77 additions & 0 deletions eg/json-server-async/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# replyAsync Example

This example demonstrates using replyAsync function. You should use
**replyAsync** instead of **reply** if you have subroutines that return
promises.

## Important Note About Conditionals

Asynchronous object macros are *not* supported within the conditional side of
`*Conditions` in RiveScript. That is, RiveScript code like the following will
not work (where the `weather` macro calls out to an HTTP API and returns a
promise with the result):

```rivescript
+ is it sunny outside
* <call>weather <get zipcode></call> == sunny => It appears it is!
- It doesn't look sunny outside.
```

Conditionals in RiveScript require their results to be immediately available
so it can check if the comparison is truthy. So, even if you use `replyAsync()`,
the `<call>` tags in conditionals only support running synchronous object
macros (ones that return a string result and not a promise).

However, asynchronous object macros can be used in the *reply* portion of the
conditional (the part on the right side of the `=>` separator). This is the
text eventually returned to the user and it can return as a promise when you use
`replyAsync()` just like if it were in a `-Reply` command.

## Running the example

To fully experience this example, you'll need to get an API key from
[OpenWeatherMap](http://openweathermap.org/appid) (don't worry, they're free!),
and edit `weatherman.js` to fill in your `APPID` near the top of the file.

```bash
npm install && node weatherman.js
```

Refer to weatherman.rive for the list of supported commands

## Using async subroutines

Whenever you have a subroutine that needs to call some sort of asynchronous
function in order to return a value back to the script, you should use promises:

```javascript
var rs = new RiveScript();
rs.setSubroutine("asyncHelper", function(rs, args) {
// RiveScript comes bundled with RSVP.js which you can
// access through RiveScript.Promise alias
// but you are free to use your own Promise implementation
return new rs.Promise(function(resolve, reject) {
resolve('hello there');
});
})
```

Async responses in RiveScript come in 2 flavors:

```javascript
// using promises
rs.replyAsync(username, message).then(function(reply) {
// good to go
}).catch(function(error){
// something went wrong
});

// or using callbacks
rs.replyAsync(username, message, this, function(error, reply) {
if (!error) {
// you can use reply here
} else {
// something went wrong, error has more info
}
});
```
227 changes: 227 additions & 0 deletions eg/json-server-async/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Asynchronous Objects Example
// See the accompanying README.md for details.

// Run this demo: `node weatherman.js`

var readline = require("readline");
var request = require("request");
var colors = require('colors');

// Configuration: get an API key from http://openweathermap.org/appid and
// put it in this variable.
const APPID = 'CHAMGEME';

// This would just be require("rivescript") if not for running this
// example from within the RiveScript project.

var express = require("express"),
cookieParser = require('cookie-parser')
bodyParser = require("body-parser"),
RiveScript = require("../../lib/rivescript.js");

var rs = new RiveScript({utf8: true});

var mytest = function(location, callback) {
callback.call(this, null, location + " parsed");
};


rs.setSubroutine("mytest", function (rs, args) {
return new rs.Promise(function(resolve, reject) {
mytest(args.join(' '), function(error, data){
if(error) {
reject(error);
} else {
resolve(data);
}
});
});
})

var getWeather = function(location, callback) {
request.get({
url: "http://api.openweathermap.org/data/2.5/weather",
qs: {
q: location,
APPID: APPID
},
json: true
}, function(error, response) {
if (response.statusCode !== 200) {
callback.call(this, response.body);
} else {
callback.call(this, null, response.body);
}
});
};


rs.setSubroutine("getWeather", function (rs, args) {
return new rs.Promise(function(resolve, reject) {
getWeather(args.join(' '), function(error, data){
if(error) {
reject(error);
} else {
resolve(data.weather[0].description);
}
});
});
});

rs.setSubroutine("checkForRain", function(rs, args) {
return new rs.Promise(function(resolve, reject) {
getWeather(args.join(' '), function(error, data){
if(error) {
console.error('');
reject(error);
} else {
var rainStatus = data.rain ? 'yup :(' : 'nope';
resolve(rainStatus);
}
});
});
});

// Create a prototypical class for our own chatbot.
var AsyncBot = function(onReady) {
var self = this;

if (APPID === 'change me') {
console.log('Error -- edit weatherman.js and provide the APPID for Open Weathermap.'.bold.yellow);
}

// Load the replies and process them.
//rs.loadDirectory("../brain", function() {
// rs.sortReplies();
// onReady();
//});


// Load the replies and process them.
rs.loadFile("weatherman.rive", function() {
rs.sortReplies();
onReady();
});

// This is a function for delivering the message to a user. Its actual
// implementation could vary; for example if you were writing an IRC chatbot
// this message could deliver a private message to a target username.
self.sendMessage = function(username, message) {
// This just logs it to the console like "[Bot] @username: message"
console.log(
["[Brick Tamland]", message].join(": ").underline.green
);
};

// This is a function for a user requesting a reply. It just proxies through
// to RiveScript.
self.getReply = function(username, message, callback) {
return rs.replyAsync(username, message, self).then(function(reply){
callback.call(this, null, reply);
}).catch(function(error) {
callback.call(this, error);
});
}
};

// Create and run the example bot.
var bot = new AsyncBot(function() {

// Set up the Express app.
var app = express();

app.use(cookieParser());

// Parse application/json inputs.
app.use(bodyParser.json());
app.set("json spaces", 4);

// Set up routes.
app.post("/reply", getReply);
app.get("/", showUsage);
app.get("*", showUsage);

// Start listening.
app.listen(2001, function() {
console.log("Listening on http://localhost:2001");
});
});

// POST to /reply to get a RiveScript reply.
function getReply(req, res) {
// Get data from the JSON post.
var message = req.body.message;
var vars = req.body.vars;
var username;

if (req.cookies.username) {
username = req.cookies.username;
} else {
username = req.connection.remoteAddress;
console.log(username);
res.cookie('username', username, { maxAge: 100000 * 60 });
}

// Make sure username and message are included.
if (typeof(message) === "undefined") {
return error(res, "message is a required key");
}

// Copy any user vars from the post into RiveScript.
if (typeof(vars) !== "undefined") {
for (var key in vars) {
if (vars.hasOwnProperty(key)) {
rs.setUservar(username, key, vars[key]);
}
}
rs.setUservar(username, "username", username);
}

var reply = bot.getReply(username, message, function(error, reply){
if (error) {
res.json({
"status": "ko",
"reply": error,
"vars": vars
});
} else {
// Get all the user's vars back out of the bot to include in the response.
vars = rs.getUservars(username);

// Send the JSON response.
res.json({
"status": "ok",
"reply": reply,
"vars": vars
});
}
});

};

// All other routes shows the usage to test the /reply route.
function showUsage(req, res) {
var egPayload = {
"username": "soandso",
"message": "Hello bot",
"vars": {
"name": "Soandso"
}
};
res.writeHead(200, {"Content-Type": "text/plain"});
res.write("Usage: curl -i \\\n");
res.write(" -H \"Content-Type: application/json\" \\\n");
res.write(" -X POST -d '" + JSON.stringify(egPayload) + "' \\\n");
res.write(" http://localhost:2001/reply");
res.end();
}

// Send a JSON error to the browser.
function error(res, message) {
res.json({
"status": "error",
"message": message
});
}


14 changes: 14 additions & 0 deletions eg/json-server-async/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "rive-weatherman",
"version": "1.0.0",
"description": "Your weather report",
"main": "weatherman.js",
"dependencies": {
"body-parser": "~1.14.2",
"colors": "^1.1.2",
"cookie-parser": "^1.4.3",
"elasticsearch": "^12.1.3",
"express": "~4.13.3",
"request": "^2.69.0"
}
}
13 changes: 13 additions & 0 deletions eg/json-server-async/weatherman.rive
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
! sub how's = how is
! sub is it going to = will it
! sub is it gonna = will it
! sub is it raining = will it rain

+ how is [the] weather in *
- <call>getWeather <star></call>

+ will it rain in *
- <call>checkForRain <star></call>

+ mytest1 *
- <call>mytest <star1></call>

0 comments on commit 03b75b2

Please sign in to comment.