UNPKG

4.54 kBJavaScriptView Raw
1import EventEmitter from 'events';
2import bodyParser from 'body-parser';
3import {Router} from 'express';
4import Elements from './Elements.js';
5import Buttons from './Buttons.js';
6import fetch from './libs/fetch.js';
7import _ from 'lodash';
8
9export {Elements, Buttons};
10
11const userCache = {};
12
13export async function wait(time) {
14 return new Promise(resolve => setTimeout(() => resolve(), time));
15}
16
17class Bot extends EventEmitter {
18 static Buttons = Buttons;
19 static Elements = Elements;
20
21 static wait = wait;
22
23 constructor(token, verification, debug = false) {
24 super();
25
26 this._token = token;
27 this._debug = debug;
28 this._verification = verification;
29 }
30
31 async send(to, message) {
32 if (this._debug) {
33 console.log({recipient: {id: to}, message: message ? message.toJSON() : message});
34 }
35
36 try {
37 await fetch('https://graph.facebook.com/v2.6/me/messages', {
38 method: 'post',
39 query: {access_token: this._token},
40 body: {recipient: {id: to}, message}
41 });
42 } catch (e) {
43 if (e.text) {
44 let text = e.text;
45 try {
46 const err = JSON.parse(e.text).error;
47 text = `${err.type || 'Unknown'}: ${err.message || 'No message'}`;
48 } catch (ee) {
49 // ignore
50 }
51
52 throw Error(text);
53 } else {
54 throw e;
55 }
56 }
57 }
58
59 async fetchUser(id, fields = 'first_name,last_name,profile_pic', cache = false) {
60 const key = id + fields;
61 let props;
62
63 if (cache && userCache[key]) {
64 props = userCache[key];
65 props.fromCache = true;
66 } else {
67 const {body} = await fetch(`https://graph.facebook.com/v2.6/${id}`, {
68 query: {access_token: this._token, fields}, json: true
69 });
70
71 props = body;
72 props.fromCache = false;
73
74 if (cache) {
75 userCache[key] = props;
76 }
77 }
78
79 return props;
80 }
81
82 async handleMessage(input) {
83 const body = JSON.parse(JSON.stringify(input));
84 const message = body.entry[0].messaging[0];
85 Object.assign(message, message.message);
86 delete message.message;
87
88 message.raw = input;
89
90 message.sender.fetch = async (fields, cache) => {
91 const props = await this.fetchUser(message.sender.id, fields, cache);
92 Object.assign(message.sender, props);
93 return message.sender;
94 };
95
96 if (message.postback) {
97 let postback = {};
98
99 try {
100 postback = JSON.parse(message.postback.payload);
101 } catch (e) {
102 // ignore
103 }
104
105 if (postback.hasOwnProperty('data')) {
106 message.postback = postback;
107 message.data = postback.data;
108 message.event = postback.event;
109
110 this.emit('postback', message.event, message, message.data);
111
112 if (postback.hasOwnProperty('event')) {
113 this.emit(message.event, message, message.data);
114 }
115 } else {
116 this.emit('invalid-postback', message, message.postback);
117 }
118
119 return;
120 }
121
122 if (message.delivery) {
123 Object.assign(message, message.delivery);
124 message.delivered = message.delivery.mids;
125
126 delete message.delivery;
127
128 this.emit('delivery', message, message.delivered);
129 return;
130 }
131
132 if (message.optin) {
133 message.param = message.optin.ref || true;
134 message.optin = message.param;
135 this.emit('optin', message, message.optin);
136 return;
137 }
138
139 const attachments = _.groupBy(message.attachments, 'type');
140
141 if (attachments.image) {
142 message.images = attachments.image.map(a => a.payload.url);
143 }
144
145 if (attachments.video) {
146 message.videos = attachments.video.map(a => a.payload.url);
147 }
148
149 if (attachments.audio) {
150 message.audio = attachments.audio.map(a => a.payload.url)[0];
151 }
152
153 if (attachments.location) {
154 const location = attachments.location[0];
155 message.location = {...location, ...location.payload.coordinates};
156 delete message.location.payload;
157 }
158
159 message.object = body.object;
160
161 delete message.attachments;
162
163 this.emit('message', message);
164 }
165
166 router() {
167 const router = new Router();
168
169 router.use(bodyParser.json());
170
171 router.get('/', (req, res) => {
172 if (req.query['hub.verify_token'] === this._verification) {
173 res.send(req.query['hub.challenge']);
174 } else {
175 res.send('Error, wrong validation token');
176 }
177 });
178
179 router.post('/', (req, res) => {
180 this.handleMessage(req.body);
181 res.send().status(200);
182 });
183
184 return router;
185 }
186}
187
188export {Bot};
189
190export default Bot;