linebot-forked
Version:
Bundit's Node.js SDK for the LINE Messaging API
297 lines (263 loc) • 8.14 kB
JavaScript
'use strict';
const EventEmitter = require('events');
const fetch = require('node-fetch');
const crypto = require('crypto');
const http = require('http');
const bodyParser = require('body-parser');
const HttpsProxyAgent = require('https-proxy-agent');
const querystring = require('querystring');
class LineBot extends EventEmitter {
constructor(options) {
super();
this.options = options || {};
this.options.channelId = options.channelId || '';
this.options.channelSecret = options.channelSecret || '';
this.options.channelAccessToken = options.channelAccessToken || '';
if (this.options.verify === undefined) {
this.options.verify = true;
}
this.headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + this.options.channelAccessToken
};
this.endpoint = 'https://api.line.me/v2/bot';
this.baseEndpoint = 'https://api.line.me/v2';
}
verify(rawBody, signature) {
const hash = crypto.createHmac('sha256', this.options.channelSecret)
.update(rawBody, 'utf8')
.digest('base64');
return hash === signature;
}
setChannelAccessToken(token) {
this.options.channelAccessToken = token;
this.headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token
};
}
parse(body) {
const that = this;
if (!body || !body.events) {
return;
}
body.events.forEach(function (event) {
event.reply = function (message) {
return that.reply(event.replyToken, message);
};
event.issueAccessToken = function () {
return that.issueAccessToken();
};
event.setChannelAccessToken = function (accessToken) {
return that.setChannelAccessToken(accessToken);
};
if (event.source) {
event.source.profile = function () {
return that.getUserProfile(event.source.userId);
};
}
if (event.message) {
event.message.content = function () {
return that.getMessageContent(event.message.id);
};
event.message.binary = function () {
return that.getContentBinary(event.message.id)
}
}
process.nextTick(function () {
that.emit(event.type, event);
});
});
}
static createMessages(message) {
if (typeof message === 'string') {
return [{ type: 'text', text: message }];
}
if (Array.isArray(message)) {
return message.map(function (m) {
if (typeof m === 'string') {
return { type: 'text', text: m };
}
return m;
});
}
return [message];
}
reply(replyToken, message) {
const body = {
replyToken: replyToken,
messages: LineBot.createMessages(message)
};
return this.post('/message/reply', body).then(function (res) {
return res.json();
});
}
push(to, message) {
if (Array.isArray(to)) {
return Promise.all(to.map(recipient => this.push(recipient, message)));
}
const body = {
to: to,
messages: LineBot.createMessages(message)
};
return this.post('/message/push', body).then(function (res) {
return res.json();
});
}
multicast(to, message) {
const body = {
to: to,
messages: LineBot.createMessages(message)
};
return this.post('/message/multicast', body).then(function (res) {
return res.json();
});
}
getUserProfile(userId) {
return this.get('/profile/' + userId).then(function (res) {
return res.json();
});
}
getMessageContent(messageId) {
return this.get('/message/' + messageId + '/content/').then(function (res) {
return res.buffer();
});
}
getContentBinary(messageId) {
return this.get('/message/' + messageId + '/content/').then(function (res) {
return res;
});
}
leaveGroup(groupId) {
return this.post('/group/' + groupId + '/leave/').then(function (res) {
return res.json();
});
}
leaveRoom(roomId) {
return this.post('/room/' + roomId + '/leave/').then(function (res) {
return res.json();
});
}
issueAccessToken() {
const body = querystring.stringify({
grant_type: 'client_credentials',
client_id: this.options.channelId,
client_secret: this.options.channelSecret
});
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
return this.postWithBaseEndPoint('/oauth/accessToken', body, headers).then(function (res) {
return res.json();
})
}
issueAccessTokenByCode(channelId, channelSecret, code, redirectUri) {
const body = querystring.stringify({
grant_type: 'authorization_code',
client_id: channelId,
client_secret: channelSecret,
code: code,
redirect_uri: redirectUri
});
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
return this.postWithBaseEndPoint('/token', body, headers, 'https://api.line.me/oauth2/v2.1').then(function (res) {
return res.json();
})
}
getProfileForLineLogin(accessToken) {
const headers = {
Authorization: `Bearer ${accessToken}`
};
return this.getWithBaseEndPoint('/profile', headers).then(function (res) {
return res.json();
});
}
get(path) {
let options = { method: 'GET', headers: this.headers };
if (!options.agent && process.env.HTTP_PROXY) {
options.agent = new HttpsProxyAgent(process.env.HTTP_PROXY);
}
return fetch(this.endpoint + path, options);
}
post(path, body) {
let options = { method: 'POST', headers: this.headers, body: JSON.stringify(body) };
if (!options.agent && process.env.HTTP_PROXY) {
options.agent = new HttpsProxyAgent(process.env.HTTP_PROXY);
}
return fetch(this.endpoint + path, options);
}
getWithBaseEndPoint(path, headers) {
let options = { method: 'GET', headers: headers };
if (!options.agent && process.env.HTTP_PROXY) {
options.agent = new HttpsProxyAgent(process.env.HTTP_PROXY);
}
return fetch(this.baseEndpoint + path, options);
}
postWithBaseEndPoint(path, body, headers, endpoint = null) {
let options = { method: 'POST', headers: headers, body: body };
if (!options.agent && process.env.HTTP_PROXY) {
options.agent = new HttpsProxyAgent(process.env.HTTP_PROXY);
}
let url = this.baseEndpoint + path;
if (endpoint) {
url = endpoint + path;
}
return fetch(url, options);
}
// Optional Express.js middleware
parser() {
const parser = bodyParser.json({
verify: function (req, res, buf, encoding) {
req.rawBody = buf.toString(encoding);
}
});
return (req, res) => {
parser(req, res, () => {
if (this.options.verify && !this.verify(req.rawBody, req.get('X-Line-Signature'))) {
return res.sendStatus(400);
}
this.parse(req.body);
return res.json({});
});
};
}
// Optional built-in http server
listen(path, port, callback) {
const parser = bodyParser.json({
verify: function (req, res, buf, encoding) {
req.rawBody = buf.toString(encoding);
}
});
const server = http.createServer((req, res) => {
const signature = req.headers['x-line-signature']; // Must be lowercase
res.setHeader('X-Powered-By', 'linebot');
if (req.method === 'POST' && req.url === path) {
parser(req, res, () => {
if (this.options.verify && !this.verify(req.rawBody, signature)) {
res.statusCode = 400;
res.setHeader('Content-Type', 'text/html; charset=utf-8');
return res.end('Bad request');
}
this.parse(req.body);
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
return res.end('{}');
});
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html; charset=utf-8');
return res.end('Not found');
}
});
return server.listen(port, callback);
}
} // class LineBot
function createBot(options) {
return new LineBot(options);
}
module.exports = createBot;
module.exports.LineBot = LineBot;