├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── app.json
├── bot.js
├── package.json
├── server
├── controllers.js
├── index.js
└── routes.js
└── test.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | extends: standard
2 | rules:
3 | arrow-parens: 0
4 | eqeqeq: 0
5 | no-return-assign: 0 # fails for arrow functions
6 | no-var: 2
7 | semi: [2, always]
8 | space-before-function-paren: [2, never]
9 | yoda: 0
10 | arrow-spacing: 2
11 | dot-location: [2, "property"]
12 | prefer-arrow-callback: 2
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Pelle Almquist
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bot-wat
2 | Hi, I'm a messenger bot based on Gary Bernhardt's Wat talk (https://www.destroyallsoftware.com/talks/wat). I'm the REPL of bots. Feed me some JavaScript.
3 |
4 |
5 |
6 | Forked from https://github.com/peralmq/koa-facebook-messenger-echo-bot
7 |
8 | # Deploy
9 | Deploy to Heroku and set two environment variables
10 | ```
11 | heroku create
12 | heroku git:remote -a
13 | heroku config set PAGE_TOKEN= VERIFY_TOKEN= -a
14 | ```
15 | Or just click:
16 |
17 | [](https://heroku.com/deploy)
18 |
19 | # Development
20 | `npm test`
21 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bot-wat",
3 | "description": "Hi, I'm a messenger bot based on Gary Bernhardt's Wat talk (https://www.destroyallsoftware.com/talks/wat).",
4 | "repository": "https://github.com/peralmq/bot-wat.git",
5 | "keywords": [
6 | "bot",
7 | "facebook",
8 | "messenger",
9 | "koa"
10 | ],
11 | "env": {
12 | "PAGE_TOKEN": {
13 | "description": "Facebook page token"
14 | },
15 | "VERIFY_TOKEN": {
16 | "description": "Facebook verification token"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/bot.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const safeEval = require('safe-eval');
4 |
5 | function echo(event) {
6 | return new Promise((resolve, reject) => {
7 | let result;
8 | try {
9 | result = safeEval(event.message.text);
10 | } catch (error) {
11 | result = error.toString();
12 | }
13 |
14 | console.log(`Evaluated ${event.message.text} to ${result}.`);
15 | resolve({
16 | messageText: result,
17 | senderId: event.sender.id
18 | });
19 | });
20 | }
21 |
22 | module.exports = {
23 | reactToMessage: echo
24 | };
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bot-wat",
3 | "version": "1.0.0",
4 | "description": "Hi, I'm a messenger bot based on Gary Bernhardt's Wat talk (https://www.destroyallsoftware.com/talks/wat).",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node server/index.js",
8 | "test": "mocha"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/peralmq/bot-wat.git"
13 | },
14 | "keywords": [
15 | "bot",
16 | "facebook",
17 | "messenger",
18 | "koa"
19 | ],
20 | "author": "Pelle Almquist ",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/peralmq/bot-wat/issues"
24 | },
25 | "homepage": "https://github.com/peralmq/bot-wat#readme",
26 | "dependencies": {
27 | "koa": "^2.0.0",
28 | "koa-bodyparser": "^3.0.0",
29 | "koa-router": "^7.0.1",
30 | "request": "^2.71.0",
31 | "safe-eval": "^0.2.0"
32 | },
33 | "devDependencies": {
34 | "eslint": "^2.5.3",
35 | "eslint-config-standard": "^5.1.0",
36 | "eslint-plugin-babel": "^3.1.0",
37 | "eslint-plugin-promise": "^1.1.0",
38 | "eslint-plugin-standard": "^1.3.1",
39 | "mocha": "^2.0.1",
40 | "should": "^8.3.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/server/controllers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const querystring = require('querystring');
3 | const request = require('request');
4 |
5 | function requestp(options) {
6 | return new Promise((resolve, reject) => {
7 | request(options, (err, response) => {
8 | if (!err) {
9 | return resolve(response);
10 | }
11 | reject(err);
12 | });
13 | });
14 | }
15 |
16 | module.exports = function(
17 | PAGE_TOKEN,
18 | VERIFY_TOKEN,
19 | bot
20 | ) {
21 | function sendMessage(options) {
22 | if (options.senderId && options.messageText) {
23 | return requestp({
24 | url: 'https://graph.facebook.com/v2.6/me/messages',
25 | qs: {access_token: process.env.PAGE_TOKEN},
26 | method: 'POST',
27 | json: {
28 | recipient: {id: options.senderId},
29 | message: {text: JSON.stringify(options.messageText)}
30 | }
31 | });
32 | }
33 | throw new Error('bot.reactToMessage needs to return senderId and messageText');
34 | }
35 |
36 | return {
37 | react: ctx => {
38 | const promises = ctx.request.body.entry
39 | .map(entry => {
40 | return entry.messaging
41 | .map(event => {
42 | return bot.reactToMessage(event)
43 | .then(sendMessage);
44 | });
45 | });
46 |
47 | Promise.all(promises)
48 | .then(_ => ctx.status = 200)
49 | .catch(_ => ctx.status = 500);
50 | },
51 | verify: ctx => {
52 | const query = querystring.parse(ctx.request.url);
53 | if (query['hub.verify_token'] === process.env.VERIFY_TOKEN) {
54 | ctx.body = query['hub.challenge'];
55 | } else {
56 | ctx.body = 'Bad verify_token';
57 | }
58 | }
59 | };
60 | };
61 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const Koa = require('koa');
3 | const server = new Koa();
4 |
5 | const bot = require('../bot');
6 | if (!process.env.PAGE_TOKEN) {
7 | throw new Error('Missing environment variable PAGE_TOKEN');
8 | }
9 | if (!process.env.VERIFY_TOKEN) {
10 | throw new Error('Missing environment variable VERIFY_TOKEN');
11 | }
12 | const controllers = require('./controllers')(
13 | process.env.PAGE_TOKEN,
14 | process.env.VERIFY_TOKEN,
15 | bot
16 | );
17 | const routes = require('./routes')(controllers);
18 |
19 | server.use(routes);
20 | server.listen(process.env.PORT || 3000);
21 |
--------------------------------------------------------------------------------
/server/routes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const router = require('koa-router')();
3 | const bodyparser = require('koa-bodyparser');
4 |
5 | module.exports = function(controllers) {
6 | router.use(bodyparser());
7 | router.get('/', controllers.verify);
8 | router.post('/', controllers.react);
9 | return router.routes();
10 | };
11 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | /* global it, describe */
2 | 'use strict';
3 | require('should');
4 |
5 | const bot = require('./bot');
6 |
7 | describe('wat', () => {
8 | describe('reactToMessage', () => {
9 | it('should evaluate JavaSript expressions', done => {
10 | bot.reactToMessage({
11 | message: {text: '1+1'},
12 | sender: {id: 1}
13 | })
14 | .then(actual => {
15 | actual.messageText.should.equal(2);
16 | done();
17 | })
18 | .catch(done);
19 | });
20 | it('should handle unsafe expressions', done => {
21 | bot.reactToMessage({
22 | message: {text: 'process'},
23 | sender: {id: 1}
24 | })
25 | .then(actual => {
26 | actual.messageText.should.match(/process is not defined/);
27 | done();
28 | })
29 | .catch(done);
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------