UNPKG

linebot-forked

Version:

Bundit's Node.js SDK for the LINE Messaging API

297 lines (263 loc) 8.14 kB
'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;