├── index.js ├── iisnode.yml ├── .travis.yml ├── .eslintrc.json ├── examples ├── echo.js ├── echo-express.js ├── echo-express-long.js └── demo.js ├── .gitignore ├── test ├── profile.test.js ├── room.test.js ├── group.test.js ├── message.test.js └── linebot.test.js ├── LICENSE ├── package.json ├── lib └── linebot.js └── README.md /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/linebot'); 2 | -------------------------------------------------------------------------------- /iisnode.yml: -------------------------------------------------------------------------------- 1 | loggingEnabled: true 2 | devErrorsEnabled: true 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "12" 5 | - "10" 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { "ecmaVersion": 6 }, 4 | "env": { 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "rules": { 10 | "indent": ["error", 2, { "SwitchCase": 1 }], 11 | "quotes": ["error", "single"], 12 | "semi": ["error", "always"], 13 | "no-console": "off" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/echo.js: -------------------------------------------------------------------------------- 1 | const linebot = require('../index.js'); 2 | 3 | const bot = linebot({ 4 | channelId: process.env.CHANNEL_ID, 5 | channelSecret: process.env.CHANNEL_SECRET, 6 | channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN 7 | }); 8 | 9 | bot.on('message', function (event) { 10 | event.reply(event.message.text).then(function (data) { 11 | console.log('Success', data); 12 | }).catch(function (error) { 13 | console.log('Error', error); 14 | }); 15 | }); 16 | 17 | bot.listen('/linewebhook', process.env.PORT || 80, function () { 18 | console.log('LineBot is running.'); 19 | }); 20 | -------------------------------------------------------------------------------- /examples/echo-express.js: -------------------------------------------------------------------------------- 1 | const linebot = require('../index.js'); 2 | const express = require('express'); 3 | 4 | const bot = linebot({ 5 | channelId: process.env.CHANNEL_ID, 6 | channelSecret: process.env.CHANNEL_SECRET, 7 | channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN 8 | }); 9 | 10 | const app = express(); 11 | 12 | const linebotParser = bot.parser(); 13 | 14 | app.post('/linewebhook', linebotParser); 15 | 16 | bot.on('message', function (event) { 17 | event.reply(event.message.text).then(function (data) { 18 | console.log('Success', data); 19 | }).catch(function (error) { 20 | console.log('Error', error); 21 | }); 22 | }); 23 | 24 | app.listen(process.env.PORT || 80, function () { 25 | console.log('LineBot is running.'); 26 | }); 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ IDEA 2 | .idea 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | -------------------------------------------------------------------------------- /test/profile.test.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const nock = require('nock'); 3 | const linebot = require('../index.js'); 4 | const assert = require('assert'); 5 | 6 | function randomUserId() { 7 | return 'U' + crypto.randomBytes(16).toString('hex'); 8 | } 9 | 10 | const line = 'https://api.line.me/v2/bot'; 11 | const userId = randomUserId(); 12 | 13 | nock(line).get(`/profile/${userId}`).reply(200, { 14 | displayName: 'Test User', 15 | userId: userId, 16 | pictureUrl: null 17 | }); 18 | 19 | const bot = linebot({ 20 | channelId: 1234567890, 21 | channelSecret: 'secret', 22 | channelAccessToken: 'token' 23 | }); 24 | 25 | describe('Profile', function() { 26 | 27 | describe('#getUserProfile()', function() { 28 | it('should return a profile.', function() { 29 | return bot.getUserProfile(userId).then((profile) => { 30 | assert.equal(profile.userId, userId); 31 | }); 32 | }); 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /examples/echo-express-long.js: -------------------------------------------------------------------------------- 1 | const linebot = require('../index.js'); 2 | const express = require('express'); 3 | const bodyParser = require('body-parser'); 4 | 5 | const bot = linebot({ 6 | channelId: process.env.CHANNEL_ID, 7 | channelSecret: process.env.CHANNEL_SECRET, 8 | channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN 9 | }); 10 | 11 | const app = express(); 12 | 13 | const parser = bodyParser.json({ 14 | verify: function (req, res, buf, encoding) { 15 | req.rawBody = buf.toString(encoding); 16 | } 17 | }); 18 | 19 | app.post('/linewebhook', parser, function (req, res) { 20 | if (!bot.verify(req.rawBody, req.get('X-Line-Signature'))) { 21 | return res.sendStatus(400); 22 | } 23 | bot.parse(req.body); 24 | return res.json({}); 25 | }); 26 | 27 | bot.on('message', function (event) { 28 | event.reply(event.message.text).then(function (data) { 29 | console.log('Success', data); 30 | }).catch(function (error) { 31 | console.log('Error', error); 32 | }); 33 | }); 34 | 35 | app.listen(process.env.PORT || 80, function () { 36 | console.log('LineBot is running.'); 37 | }); 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Bundit Jitkongchuen 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linebot", 3 | "version": "1.6.1", 4 | "description": "Node.js SDK for the LINE Messaging API", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js", 8 | "lib/**" 9 | ], 10 | "scripts": { 11 | "start": "node examples/demo.js", 12 | "pretest": "eslint index.js lib/**/*.js test/**/*.js", 13 | "test": "mocha --timeout 5000 --exit", 14 | "lint": "eslint index.js lib/**/*.js test/**/*.js examples/**/*.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/boybundit/linebot.git" 19 | }, 20 | "keywords": [ 21 | "LINE", 22 | "Messaging", 23 | "API", 24 | "chat", 25 | "bot" 26 | ], 27 | "author": "Bundit Jitkongchuen", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/boybundit/linebot/issues" 31 | }, 32 | "homepage": "https://github.com/boybundit/linebot#readme", 33 | "dependencies": { 34 | "body-parser": "^1.18.2", 35 | "debug": "^4.2.0", 36 | "node-fetch": "^2.6.1" 37 | }, 38 | "devDependencies": { 39 | "eslint": "^7.10.0", 40 | "mocha": "^8.1.3", 41 | "nock": "^13.0.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/room.test.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const nock = require('nock'); 3 | const linebot = require('../index.js'); 4 | const assert = require('assert'); 5 | 6 | function randomUserId() { 7 | return 'U' + crypto.randomBytes(16).toString('hex'); 8 | } 9 | 10 | const line = 'https://api.line.me/v2/bot'; 11 | const roomId = randomUserId().replace('U', 'R'); 12 | const userId = randomUserId(); 13 | 14 | nock(line).get(`/room/${roomId}/member/${userId}`).reply(200, { 15 | displayName: 'Test User', 16 | userId: userId, 17 | pictureUrl: null 18 | }); 19 | 20 | nock(line).get(`/room/${roomId}/members/ids`).reply(200, { 21 | memberIds: [userId], 22 | next: 'token2' 23 | }); 24 | 25 | nock(line).get(`/room/${roomId}/members/ids?start=token2`).reply(200, { 26 | memberIds: [randomUserId(), randomUserId()], 27 | next: 'token3' 28 | }); 29 | 30 | nock(line).get(`/room/${roomId}/members/ids?start=token3`).reply(200, { 31 | memberIds: [randomUserId(), randomUserId()] 32 | }); 33 | 34 | nock(line).post(`/room/${roomId}/leave`).reply(200, {}); 35 | 36 | const bot = linebot({ 37 | channelId: 1234567890, 38 | channelSecret: 'secret', 39 | channelAccessToken: 'token' 40 | }); 41 | 42 | describe('Room', function() { 43 | 44 | describe('#getRoomMemberProfile()', function() { 45 | it('should return a profile.', function() { 46 | return bot.getRoomMemberProfile(roomId, userId).then((profile) => { 47 | assert.equal(profile.userId, userId); 48 | assert.equal(profile.roomId, roomId); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('#getRoomMember()', function() { 54 | it('should return a list of member.', function() { 55 | return bot.getRoomMember(roomId).then((roomMember) => { 56 | assert.equal(roomMember.memberIds.length, 5); 57 | assert.equal(roomMember.memberIds[0], userId); 58 | }); 59 | }); 60 | }); 61 | 62 | describe('#leaveRoom()', function() { 63 | it('should return an empty object.', function() { 64 | return bot.leaveRoom(roomId).then((result) => { 65 | assert.deepEqual(result, {}); 66 | }); 67 | }); 68 | }); 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /test/group.test.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const nock = require('nock'); 3 | const linebot = require('../index.js'); 4 | const assert = require('assert'); 5 | 6 | function randomUserId() { 7 | return 'U' + crypto.randomBytes(16).toString('hex'); 8 | } 9 | 10 | const line = 'https://api.line.me/v2/bot'; 11 | const groupId = randomUserId().replace('U', 'C'); 12 | const userId = randomUserId(); 13 | 14 | nock(line).get(`/group/${groupId}/member/${userId}`).reply(200, { 15 | displayName: 'Test User', 16 | userId: userId, 17 | pictureUrl: null 18 | }); 19 | 20 | nock(line).get(`/group/${groupId}/members/ids`).reply(200, { 21 | memberIds: [userId], 22 | next: 'token2' 23 | }); 24 | 25 | nock(line).get(`/group/${groupId}/members/ids?start=token2`).reply(200, { 26 | memberIds: [randomUserId(), randomUserId()], 27 | next: 'token3' 28 | }); 29 | 30 | nock(line).get(`/group/${groupId}/members/ids?start=token3`).reply(200, { 31 | memberIds: [randomUserId(), randomUserId()] 32 | }); 33 | 34 | nock(line).post(`/group/${groupId}/leave`).reply(200, {}); 35 | 36 | const bot = linebot({ 37 | channelId: 1234567890, 38 | channelSecret: 'secret', 39 | channelAccessToken: 'token' 40 | }); 41 | 42 | describe('Group', function() { 43 | 44 | describe('#getGroupMemberProfile()', function() { 45 | it('should return a profile.', function() { 46 | return bot.getGroupMemberProfile(groupId, userId).then((profile) => { 47 | assert.equal(profile.userId, userId); 48 | assert.equal(profile.groupId, groupId); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('#getGroupMember()', function() { 54 | it('should return a list of member.', function() { 55 | return bot.getGroupMember(groupId).then((groupMember) => { 56 | assert.equal(groupMember.memberIds.length, 5); 57 | assert.equal(groupMember.memberIds[0], userId); 58 | }); 59 | }); 60 | }); 61 | 62 | describe('#leaveGroup()', function() { 63 | it('should return an empty object.', function() { 64 | return bot.leaveGroup(groupId).then((result) => { 65 | assert.deepEqual(result, {}); 66 | }); 67 | }); 68 | }); 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /test/message.test.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | const nock = require('nock'); 3 | const linebot = require('../index.js'); 4 | const assert = require('assert'); 5 | 6 | function randomUserId() { 7 | return 'U' + crypto.randomBytes(16).toString('hex'); 8 | } 9 | 10 | const line = 'https://api.line.me/v2/bot'; 11 | const lineData = 'https://api-data.line.me/v2/bot'; 12 | const userId = randomUserId(); 13 | const userId2 = randomUserId(); 14 | const userId3 = randomUserId(); 15 | const replyToken = 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA'; 16 | 17 | nock(line) 18 | .post('/message/reply', { 19 | replyToken: replyToken, 20 | messages: [{ type: 'text', text: 'message' }] 21 | }) 22 | .reply(200, {}); 23 | 24 | nock(line) 25 | .post('/message/reply', { 26 | replyToken: replyToken, 27 | messages: [{ type: 'text', text: 'message1' }, { type: 'text', text: 'message2' }] 28 | }) 29 | .reply(200, {}); 30 | 31 | nock(line) 32 | .persist() 33 | .post('/message/push', { 34 | to: /^U.*/, 35 | messages: [{ type: 'text', text: 'message' }] 36 | }) 37 | .reply(200, {}); 38 | 39 | nock(line) 40 | .post('/message/push', { 41 | to: userId, 42 | messages: [{ type: 'text', text: 'message1' }, { type: 'text', text: 'message2' }] 43 | }) 44 | .reply(200, {}); 45 | 46 | nock(line) 47 | .post('/message/multicast', { 48 | to: [userId, userId2, userId3], 49 | messages: [{ type: 'text', text: 'message' }] 50 | }) 51 | .reply(200, {}); 52 | 53 | nock(line) 54 | .post('/message/broadcast', { 55 | messages: [{ type: 'text', text: 'message' }] 56 | }) 57 | .reply(200, {}); 58 | 59 | const content = crypto.randomBytes(16); 60 | nock(lineData).get('/message/messageId/content').reply(200, content); 61 | 62 | const bot = linebot({ 63 | channelId: 1234567890, 64 | channelSecret: 'secret', 65 | channelAccessToken: 'token' 66 | }); 67 | 68 | describe('Message', function() { 69 | 70 | describe('#reply()', function() { 71 | it('should return an empty object.', function() { 72 | return bot.reply(replyToken, 'message').then((result) => { 73 | assert.deepEqual(result, {}); 74 | }); 75 | }); 76 | it('should support message array.', function() { 77 | return bot.reply(replyToken, ['message1', 'message2']).then((result) => { 78 | assert.deepEqual(result, {}); 79 | }); 80 | }); 81 | }); 82 | 83 | describe('#push()', function() { 84 | it('should return an empty object.', function() { 85 | return bot.push(userId, 'message').then((result) => { 86 | assert.deepEqual(result, {}); 87 | }); 88 | }); 89 | it('should support message array.', function() { 90 | return bot.push(userId, ['message1', 'message2']).then((result) => { 91 | assert.deepEqual(result, {}); 92 | }); 93 | }); 94 | it('should resolve as multiple promises.', function () { 95 | return bot.push([userId, userId2, userId3], 'message').then(function (results) { 96 | assert.equal(results.length, 3); 97 | }); 98 | }); 99 | }); 100 | 101 | describe('#multicast()', function() { 102 | it('should return an empty object.', function() { 103 | return bot.multicast([userId, userId2, userId3], 'message').then((result) => { 104 | assert.deepEqual(result, {}); 105 | }); 106 | }); 107 | }); 108 | 109 | describe('#broadcast()', function() { 110 | it('should return an empty object.', function() { 111 | return bot.broadcast('message').then((result) => { 112 | assert.deepEqual(result, {}); 113 | }); 114 | }); 115 | }); 116 | 117 | describe('#getMessageContent()', function() { 118 | it('should return a buffer.', function() { 119 | return bot.getMessageContent('messageId').then((buffer) => { 120 | assert.equal(buffer.toString('hex'), content.toString('hex')); 121 | }); 122 | }); 123 | }); 124 | 125 | }); 126 | -------------------------------------------------------------------------------- /test/linebot.test.js: -------------------------------------------------------------------------------- 1 | const linebot = require('../index.js'); 2 | const assert = require('assert'); 3 | const crypto = require('crypto'); 4 | const fetch = require('node-fetch'); 5 | const nock = require('nock'); 6 | 7 | const line = 'https://api.line.me/v2/bot'; 8 | 9 | const bot = linebot({ 10 | channelId: 1234567890, 11 | channelSecret: 'secret', 12 | channelAccessToken: 'token' 13 | }); 14 | 15 | const req = {}; 16 | req.headers = { 17 | 'Content-Type': 'application/json', 18 | Authorization: 'Bearer token', 19 | 'X-Line-Signature': 'signature' 20 | }; 21 | req.body = { 22 | events: [{ 23 | replyToken: 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA', 24 | type: 'message', 25 | timestamp: 1462629479859, 26 | source: { 27 | type: 'user', 28 | userId: 'U206d25c2ea6bd87c17655609a1c37cb8' 29 | }, 30 | message: { 31 | id: '325708', 32 | type: 'text', 33 | text: 'Hello, world' 34 | } 35 | }] 36 | }; 37 | req.rawBody = JSON.stringify(req.body); 38 | req.headers['X-Line-Signature'] = crypto.createHmac('sha256', 'secret').update(req.rawBody, 'utf8').digest('base64'); 39 | 40 | describe('linebot', function () { 41 | 42 | describe('#constructor()', function () { 43 | it('should create a new LineBot instance.', function () { 44 | assert.equal(linebot.LineBot, bot.constructor); 45 | }); 46 | it('should have options as specified.', function () { 47 | assert.equal(bot.options.verify, true); 48 | }); 49 | }); 50 | 51 | describe('#verify()', function () { 52 | it('should return true when the signature is correct.', function () { 53 | const res = bot.verify(req.rawBody, req.headers['X-Line-Signature']); 54 | assert.equal(res, true); 55 | }); 56 | it('should return false when the signature is incorrect.', function () { 57 | const res = bot.verify(req.rawBody, 'random signature'); 58 | assert.equal(res, false); 59 | }); 60 | }); 61 | 62 | describe('#parse()', function () { 63 | it('should raise message event.', function (done) { 64 | const localBot = linebot({}); 65 | localBot.on('message', function (event) { 66 | assert.equal(event, req.body.events[0]); 67 | assert.equal(typeof event.reply, 'function'); 68 | if (event.source) { 69 | assert.equal(typeof event.source.profile, 'function'); 70 | } 71 | if (event.message) { 72 | assert.equal(typeof event.message.content, 'function'); 73 | } 74 | done(); 75 | }); 76 | localBot.parse(req.body); 77 | }); 78 | }); 79 | 80 | describe('#get()', function () { 81 | it('should return a promise.', function () { 82 | const path = '/a/random/path'; 83 | nock(line).get(path).reply(404); 84 | const res = bot.get(path).then(function (res) { 85 | assert.equal(res.status, 404); 86 | }); 87 | assert.equal(res.constructor, Promise); 88 | return res; 89 | }); 90 | }); 91 | 92 | describe('#post()', function () { 93 | it('should return a promise.', function () { 94 | const path = '/a/random/path'; 95 | const body = { 96 | head: 'This is the head of the body. Do you not like it?' 97 | }; 98 | nock(line).post(path, body).reply(200); 99 | const res = bot.post(path, body).then(function (res) { 100 | assert.equal(res.status, 200); 101 | }); 102 | assert.equal(res.constructor, Promise); 103 | return res; 104 | }); 105 | }); 106 | 107 | describe('#parser()', function () { 108 | it('should return a function that expects 2 arguments.', function () { 109 | const parser = bot.parser(); 110 | assert.equal(typeof parser, 'function'); 111 | assert.equal(parser.length, 2); 112 | }); 113 | }); 114 | 115 | describe('#listen()', function () { 116 | it('should expect 3 arguments.', function () { 117 | assert.equal(typeof bot.listen, 'function'); 118 | assert.equal(bot.listen.length, 3); 119 | }); 120 | it('should start http server.', function (done) { 121 | bot.listen('/linewebhook', 3000, function () { 122 | done(); 123 | }); 124 | }); 125 | it('should handle POST request and return empty object.', function (done) { 126 | fetch('http://localhost:3000/linewebhook', { method: 'POST', headers: req.headers, body: JSON.stringify(req.body) }).then(function (res) { 127 | assert.equal(res.status, 200); 128 | return res.json(); 129 | }).then(function (data) { 130 | assert.deepEqual(data, {}); 131 | done(); 132 | }); 133 | }); 134 | }); 135 | 136 | }); 137 | -------------------------------------------------------------------------------- /examples/demo.js: -------------------------------------------------------------------------------- 1 | const linebot = require('../index.js'); 2 | 3 | const bot = linebot({ 4 | channelId: process.env.CHANNEL_ID, 5 | channelSecret: process.env.CHANNEL_SECRET, 6 | channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN, 7 | verify: true // default=true 8 | }); 9 | 10 | bot.on('message', function (event) { 11 | switch (event.message.type) { 12 | case 'text': 13 | switch (event.message.text) { 14 | case 'Me': 15 | event.source.profile().then(function (profile) { 16 | return event.reply('Hello ' + profile.displayName + ' ' + profile.userId); 17 | }); 18 | break; 19 | case 'Member': 20 | event.source.member().then(function (member) { 21 | return event.reply(JSON.stringify(member)); 22 | }); 23 | break; 24 | case 'Picture': 25 | event.reply({ 26 | type: 'image', 27 | originalContentUrl: 'https://d.line-scdn.net/stf/line-lp/family/en-US/190X190_line_me.png', 28 | previewImageUrl: 'https://d.line-scdn.net/stf/line-lp/family/en-US/190X190_line_me.png' 29 | }); 30 | break; 31 | case 'Location': 32 | event.reply({ 33 | type: 'location', 34 | title: 'LINE Plus Corporation', 35 | address: '1 Empire tower, Sathorn, Bangkok 10120, Thailand', 36 | latitude: 13.7202068, 37 | longitude: 100.5298698 38 | }); 39 | break; 40 | case 'Push': 41 | bot.push('U17448c796a01b715d293c34810985a4c', ['Hey!', 'สวัสดี ' + String.fromCharCode(0xD83D, 0xDE01)]); 42 | break; 43 | case 'Push2': 44 | bot.push('Cba71ba25dafbd6a1472c655fe22979e2', 'Push to group'); 45 | break; 46 | case 'Multicast': 47 | bot.push(['U17448c796a01b715d293c34810985a4c', 'Cba71ba25dafbd6a1472c655fe22979e2'], 'Multicast!'); 48 | break; 49 | case 'Broadcast': 50 | bot.broadcast('Broadcast!'); 51 | break; 52 | case 'Confirm': 53 | event.reply({ 54 | type: 'template', 55 | altText: 'this is a confirm template', 56 | template: { 57 | type: 'confirm', 58 | text: 'Are you sure?', 59 | actions: [{ 60 | type: 'message', 61 | label: 'Yes', 62 | text: 'yes' 63 | }, { 64 | type: 'message', 65 | label: 'No', 66 | text: 'no' 67 | }] 68 | } 69 | }); 70 | break; 71 | case 'Multiple': 72 | return event.reply(['Line 1', 'Line 2', 'Line 3', 'Line 4', 'Line 5']); 73 | case 'Total followers': 74 | bot.getTotalFollowers().then((result) => { 75 | event.reply('Total followers: ' + result.followers); 76 | }); 77 | break; 78 | case 'Quota': 79 | bot.getQuota().then((result) => { 80 | event.reply('Quota: ' + result.value); 81 | }); 82 | break; 83 | case 'Total reply': 84 | bot.getTotalReplyMessages().then((result) => { 85 | event.reply('Total reply messages: ' + result.success); 86 | }); 87 | break; 88 | case 'Version': 89 | event.reply('linebot@' + require('../package.json').version); 90 | break; 91 | default: 92 | event.reply(event.message.text).then(function (data) { 93 | console.log('Success', data); 94 | }).catch(function (error) { 95 | console.log('Error', error); 96 | }); 97 | break; 98 | } 99 | break; 100 | case 'image': 101 | event.message.content().then(function (data) { 102 | const s = data.toString('hex').substring(0, 32); 103 | return event.reply('Nice picture! ' + s); 104 | }).catch(function (err) { 105 | return event.reply(err.toString()); 106 | }); 107 | break; 108 | case 'video': 109 | event.reply('Nice video!'); 110 | break; 111 | case 'audio': 112 | event.reply('Nice audio!'); 113 | break; 114 | case 'location': 115 | event.reply(['That\'s a good location!', 'Lat:' + event.message.latitude, 'Long:' + event.message.longitude]); 116 | break; 117 | case 'sticker': 118 | event.reply({ 119 | type: 'sticker', 120 | packageId: 1, 121 | stickerId: 1 122 | }); 123 | break; 124 | default: 125 | event.reply('Unknown message: ' + JSON.stringify(event)); 126 | break; 127 | } 128 | }); 129 | 130 | bot.on('follow', function (event) { 131 | event.reply('follow: ' + event.source.userId); 132 | }); 133 | 134 | bot.on('unfollow', function (event) { 135 | event.reply('unfollow: ' + event.source.userId); 136 | }); 137 | 138 | bot.on('join', function (event) { 139 | if(event.source.groupId) { 140 | event.reply('join group: ' + event.source.groupId); 141 | } 142 | if(event.source.roomId) { 143 | event.reply('join room: ' + event.source.roomId); 144 | } 145 | }); 146 | 147 | bot.on('leave', function (event) { 148 | if(event.source.groupId) { 149 | console.log('leave group: ' + event.source.groupId); 150 | } 151 | if(event.source.roomId) { 152 | console.log('leave room: ' + event.source.roomId); 153 | } 154 | }); 155 | 156 | bot.on('memberJoined', function (event) { 157 | event.source.profile().then(function (/*profile*/) { 158 | if(event.source.type === 'group') { 159 | event.reply('memberJoined: Welcome to the group.'); 160 | } 161 | if(event.source.type === 'room') { 162 | event.reply('memberJoined: Welcome to the room.'); 163 | } 164 | }); 165 | }); 166 | 167 | bot.on('memberLeft', function (/*event*/) { 168 | console.log('memberLeft: Goodbye.'); 169 | }); 170 | 171 | bot.on('postback', function (event) { 172 | event.reply('postback: ' + event.postback.data); 173 | }); 174 | 175 | bot.on('beacon', function (event) { 176 | event.reply('beacon: ' + event.beacon.hwid); 177 | }); 178 | 179 | bot.listen('/linewebhook', process.env.PORT || 80, function () { 180 | console.log('LineBot is running.'); 181 | }); 182 | -------------------------------------------------------------------------------- /lib/linebot.js: -------------------------------------------------------------------------------- 1 | 'use strict'; // Required to use class in node v4 2 | 3 | const EventEmitter = require('events'); 4 | const fetch = require('node-fetch'); 5 | const crypto = require('crypto'); 6 | const http = require('http'); 7 | const bodyParser = require('body-parser'); 8 | const debug = require('debug')('linebot'); 9 | 10 | class LineBot extends EventEmitter { 11 | 12 | constructor(options) { 13 | super(); 14 | this.options = options || {}; 15 | this.options.channelId = options.channelId || ''; 16 | this.options.channelSecret = options.channelSecret || ''; 17 | this.options.channelAccessToken = options.channelAccessToken || ''; 18 | if (this.options.verify === undefined) { 19 | this.options.verify = true; 20 | } 21 | this.headers = { 22 | Accept: 'application/json', 23 | 'Content-Type': 'application/json', 24 | Authorization: 'Bearer ' + this.options.channelAccessToken 25 | }; 26 | this.endpoint = 'https://api.line.me/v2/bot'; 27 | this.dataEndpoint = 'https://api-data.line.me/v2/bot'; 28 | } 29 | 30 | verify(rawBody, signature) { 31 | const hash = crypto.createHmac('sha256', this.options.channelSecret) 32 | .update(rawBody, 'utf8') 33 | .digest('base64'); 34 | // Constant-time comparison to prevent timing attack. 35 | if (hash.length !== signature.length) { 36 | return false; 37 | } 38 | let res = 0; 39 | for (let i = 0; i < hash.length; i++) { 40 | res |= (hash.charCodeAt(i) ^ signature.charCodeAt(i)); 41 | } 42 | return res === 0; 43 | } 44 | 45 | parse(body) { 46 | const that = this; 47 | if (!body || !body.events) { 48 | return; 49 | } 50 | body.events.forEach(function(event) { 51 | debug('%O', event); 52 | event.reply = function (message) { 53 | return that.reply(event.replyToken, message); 54 | }; 55 | if (event.source) { 56 | event.source.profile = function() { 57 | if (event.source.type === 'group') { 58 | return that.getGroupMemberProfile(event.source.groupId, event.source.userId); 59 | } 60 | if (event.source.type === 'room') { 61 | return that.getRoomMemberProfile(event.source.roomId, event.source.userId); 62 | } 63 | return that.getUserProfile(event.source.userId); 64 | }; 65 | event.source.member = function() { 66 | if (event.source.type === 'group') { 67 | return that.getGroupMember(event.source.groupId); 68 | } 69 | if (event.source.type === 'room') { 70 | return that.getRoomMember(event.source.roomId); 71 | } 72 | }; 73 | } 74 | if (event.message) { 75 | event.message.content = function() { 76 | return that.getMessageContent(event.message.id); 77 | }; 78 | } 79 | process.nextTick(function() { 80 | that.emit(event.type, event); 81 | }); 82 | }); 83 | } 84 | 85 | static createMessages(message) { 86 | if (typeof message === 'string') { 87 | return [{ type: 'text', text: message }]; 88 | } 89 | if (Array.isArray(message)) { 90 | return message.map(function(m) { 91 | if (typeof m === 'string') { 92 | return { type: 'text', text: m }; 93 | } 94 | return m; 95 | }); 96 | } 97 | return [message]; 98 | } 99 | 100 | reply(replyToken, message) { 101 | const url = '/message/reply'; 102 | const body = { 103 | replyToken: replyToken, 104 | messages: LineBot.createMessages(message) 105 | }; 106 | debug('POST %s', url); 107 | debug('%O', body); 108 | return this.post(url, body).then(res => res.json()).then((result) => { 109 | debug(result); 110 | return result; 111 | }); 112 | } 113 | 114 | push(to, message) { 115 | const url = '/message/push'; 116 | if (Array.isArray(to)) { 117 | return Promise.all(to.map(recipient => this.push(recipient, message))); 118 | } 119 | const body = { 120 | to: to, 121 | messages: LineBot.createMessages(message) 122 | }; 123 | debug('POST %s', url); 124 | debug('%O', body); 125 | return this.post(url, body).then(res => res.json()).then((result) => { 126 | debug('%O', result); 127 | return result; 128 | }); 129 | } 130 | 131 | multicast(to, message) { 132 | const url = '/message/multicast'; 133 | const body = { 134 | to: to, 135 | messages: LineBot.createMessages(message) 136 | }; 137 | debug('POST %s', url); 138 | debug('%O', body); 139 | return this.post(url, body).then(res => res.json()).then((result) => { 140 | debug('%O', result); 141 | return result; 142 | }); 143 | } 144 | 145 | broadcast(message){ 146 | const url = '/message/broadcast'; 147 | const body = { 148 | messages: LineBot.createMessages(message) 149 | }; 150 | debug('POST %s', url); 151 | debug('%O', body); 152 | return this.post(url, body).then(res => res.json()).then((result) => { 153 | debug('%O', result); 154 | return result; 155 | }); 156 | } 157 | 158 | getMessageContent(messageId) { 159 | const url = `/message/${messageId}/content`; 160 | debug('GET %s', url); 161 | return this.getData(url).then(res => res.buffer()).then((buffer) => { 162 | debug(buffer.toString('hex')); 163 | return buffer; 164 | }); 165 | } 166 | 167 | getUserProfile(userId) { 168 | const url = `/profile/${userId}`; 169 | debug('GET %s', url); 170 | return this.get(url).then(res => res.json()).then((profile) => { 171 | debug('%O', profile); 172 | return profile; 173 | }); 174 | } 175 | 176 | getGroupMemberProfile(groupId, userId) { 177 | const url = `/group/${groupId}/member/${userId}`; 178 | debug('GET %s', url); 179 | return this.get(url).then(res => res.json()).then((profile) => { 180 | debug('%O', profile); 181 | profile.groupId = groupId; 182 | return profile; 183 | }); 184 | } 185 | 186 | getGroupMember(groupId, next) { 187 | const url = `/group/${groupId}/members/ids` + (next ? `?start=${next}` : ''); 188 | debug('GET %s', url); 189 | return this.get(url).then(res => res.json()).then((groupMember) => { 190 | debug('%O', groupMember); 191 | if (groupMember.next) { 192 | return this.getGroupMember(groupId, groupMember.next).then((nextGroupMember) => { 193 | groupMember.memberIds = groupMember.memberIds.concat(nextGroupMember.memberIds); 194 | delete groupMember.next; 195 | return groupMember; 196 | }); 197 | } 198 | delete groupMember.next; 199 | return groupMember; 200 | }); 201 | } 202 | 203 | leaveGroup(groupId) { 204 | const url = `/group/${groupId}/leave`; 205 | debug('POST %s', url); 206 | return this.post(url).then(res => res.json()).then((result) => { 207 | debug('%O', result); 208 | return result; 209 | }); 210 | } 211 | 212 | getRoomMemberProfile(roomId, userId) { 213 | const url = `/room/${roomId}/member/${userId}`; 214 | debug('GET %s', url); 215 | return this.get(url).then(res => res.json()).then((profile) => { 216 | debug('%O', profile); 217 | profile.roomId = roomId; 218 | return profile; 219 | }); 220 | } 221 | 222 | getRoomMember(roomId, next) { 223 | const url = `/room/${roomId}/members/ids` + (next ? `?start=${next}` : ''); 224 | debug('GET %s', url); 225 | return this.get(url).then(res => res.json()).then((roomMember) => { 226 | debug('%O', roomMember); 227 | if (roomMember.next) { 228 | return this.getRoomMember(roomId, roomMember.next).then((nextRoomMember) => { 229 | roomMember.memberIds = roomMember.memberIds.concat(nextRoomMember.memberIds); 230 | delete roomMember.next; 231 | return roomMember; 232 | }); 233 | } 234 | delete roomMember.next; 235 | return roomMember; 236 | }); 237 | } 238 | 239 | leaveRoom(roomId) { 240 | const url = `/room/${roomId}/leave`; 241 | debug('POST %s', url); 242 | return this.post(url).then(res => res.json()).then((result) => { 243 | debug('%O', result); 244 | return result; 245 | }); 246 | } 247 | 248 | getTotalFollowers(date) { 249 | if (date == null) { 250 | date = yesterday(); 251 | } 252 | const url = `/insight/followers?date=${date}`; 253 | debug('GET %s', url); 254 | return this.get(url).then(res => res.json()).then((result) => { 255 | debug('%O', result); 256 | return result; 257 | }); 258 | } 259 | 260 | getQuota() { 261 | const url = '/message/quota'; 262 | debug('GET %s', url); 263 | return this.get(url).then(res => res.json()).then((result) => { 264 | debug('%O', result); 265 | return result; 266 | }); 267 | } 268 | 269 | getTotalReplyMessages(date) { 270 | return this.getTotalMessages(date, 'reply'); 271 | } 272 | 273 | getTotalPushMessages(date) { 274 | return this.getTotalMessages(date, 'push'); 275 | } 276 | 277 | getTotalBroadcastMessages(date) { 278 | return this.getTotalMessages(date, 'broadcast'); 279 | } 280 | 281 | getTotalMulticastMessages(date) { 282 | return this.getTotalMessages(date, 'multicast'); 283 | } 284 | 285 | getTotalMessages(date, type) { 286 | if (date == null) { 287 | date = yesterday(); 288 | } 289 | const url = `/message/delivery/${type}?date=${date}`; 290 | debug('GET %s', url); 291 | return this.get(url).then(res => res.json()).then((result) => { 292 | debug('%O', result); 293 | return result; 294 | }); 295 | } 296 | 297 | get(path) { 298 | const url = this.endpoint + path; 299 | const options = { method: 'GET', headers: this.headers }; 300 | return fetch(url, options); 301 | } 302 | 303 | getData(path) { 304 | const url = this.dataEndpoint + path; 305 | const options = { method: 'GET', headers: this.headers }; 306 | return fetch(url, options); 307 | } 308 | 309 | post(path, body) { 310 | const url = this.endpoint + path; 311 | const options = { method: 'POST', headers: this.headers, body: JSON.stringify(body) }; 312 | return fetch(url, options); 313 | } 314 | 315 | // Optional Express.js middleware 316 | parser() { 317 | const parser = bodyParser.json({ 318 | verify: function (req, res, buf, encoding) { 319 | req.rawBody = buf.toString(encoding); 320 | } 321 | }); 322 | return (req, res) => { 323 | parser(req, res, () => { 324 | if (this.options.verify && !this.verify(req.rawBody, req.get('X-Line-Signature'))) { 325 | return res.sendStatus(400); 326 | } 327 | this.parse(req.body); 328 | return res.json({}); 329 | }); 330 | }; 331 | } 332 | 333 | // Optional built-in http server 334 | listen(path, port, callback) { 335 | const parser = bodyParser.json({ 336 | verify: function (req, res, buf, encoding) { 337 | req.rawBody = buf.toString(encoding); 338 | } 339 | }); 340 | const server = http.createServer((req, res) => { 341 | const signature = req.headers['x-line-signature']; // Must be lowercase 342 | res.setHeader('X-Powered-By', 'linebot'); 343 | if (req.method === 'POST' && req.url === path) { 344 | parser(req, res, () => { 345 | if (this.options.verify && !this.verify(req.rawBody, signature)) { 346 | res.statusCode = 400; 347 | res.setHeader('Content-Type', 'text/html; charset=utf-8'); 348 | return res.end('Bad request'); 349 | } 350 | this.parse(req.body); 351 | res.statusCode = 200; 352 | res.setHeader('Content-Type', 'application/json'); 353 | return res.end('{}'); 354 | }); 355 | } else { 356 | res.statusCode = 404; 357 | res.setHeader('Content-Type', 'text/html; charset=utf-8'); 358 | return res.end('Not found'); 359 | } 360 | }); 361 | return server.listen(port, callback); 362 | } 363 | 364 | } // class LineBot 365 | 366 | function createBot(options) { 367 | return new LineBot(options); 368 | } 369 | 370 | function yesterday() { 371 | const tempDate = new Date(); 372 | tempDate.setDate(tempDate.getDate() - 1); 373 | const yesterday = tempDate.toLocaleString('en-US', { 374 | timeZone: 'Asia/Tokyo', 375 | day: '2-digit', 376 | month: '2-digit', 377 | year: 'numeric' 378 | }); 379 | return yesterday.substr(6, 4) + yesterday.substr(0, 2) + yesterday.substr(3, 2); 380 | } 381 | 382 | module.exports = createBot; 383 | module.exports.LineBot = LineBot; 384 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linebot 2 | 3 | [![NPM Version][npm-image]][npm-url] 4 | [![NPM Downloads][downloads-image]][downloads-url] 5 | [![NPM Dependencies][dependencies-image]][dependencies-url] 6 | [![Build][travis-image]][travis-url] 7 | 8 | 🤖 SDK for the LINE Messaging API for Node.js 9 | - Come with built-in server for quick setup 10 | - Provide convenient addon functions to [event object](#event-object) 11 | 12 | # About LINE Messaging API 13 | 14 | Please refer to the official API documents for details. 15 | - Developer Documents - https://developers.line.biz/en/docs/ 16 | - API Reference - https://developers.line.biz/en/reference/messaging-api/ 17 | 18 | # Installation 19 | 20 | ```bash 21 | $ npm install linebot --save 22 | ``` 23 | 24 | # Usage 25 | 26 | ```js 27 | var linebot = require('linebot'); 28 | 29 | var bot = linebot({ 30 | channelId: CHANNEL_ID, 31 | channelSecret: CHANNEL_SECRET, 32 | channelAccessToken: CHANNEL_ACCESS_TOKEN 33 | }); 34 | 35 | bot.on('message', function (event) { 36 | event.reply(event.message.text).then(function (data) { 37 | // success 38 | }).catch(function (error) { 39 | // error 40 | }); 41 | }); 42 | 43 | bot.listen('/linewebhook', 3000); 44 | ``` 45 | 46 | ### Using with your own [Express.js][express-url] server 47 | ```js 48 | const app = express(); 49 | const linebotParser = bot.parser(); 50 | app.post('/linewebhook', linebotParser); 51 | app.listen(3000); 52 | ``` 53 | 54 | See [`examples`](examples) folder for more examples. 55 | 56 | # API 57 | 58 | ## LineBot object 59 | 60 | ### linebot(config) 61 | Create LineBot instance with specified configuration. 62 | ```js 63 | var bot = linebot({ 64 | channelId: CHANNEL_ID, 65 | channelSecret: CHANNEL_SECRET, 66 | channelAccessToken: CHANNEL_ACCESS_TOKEN, 67 | verify: true // Verify 'X-Line-Signature' header (default=true) 68 | }); 69 | ``` 70 | 71 | ### LineBot.listen(webHookPath, port, callback) 72 | 73 | Start built-in http server on the specified `port`, 74 | and accept POST request callback on the specified `webHookPath`. 75 | 76 | This method is provided for convenience. 77 | You can write you own server and use `verify` and `parse` methods to process webhook events. 78 | See [`examples/echo-express-long.js`](examples/echo-express-long.js) for example. 79 | 80 | ### LineBot.parser() 81 | 82 | Create [Express.js][express-url] middleware to parse the request. 83 | 84 | The parser assumes that the request body has never been parsed by any body parser before, 85 | so it must be placed BEFORE any generic body parser e.g. `app.use(bodyParser.json());` 86 | 87 | ### LineBot.verify(rawBody, signature) 88 | 89 | Verify `X-Line-Signature` header. 90 | 91 | ### LineBot.parse(body) 92 | 93 | Process incoming webhook request, and raise an event. 94 | 95 | ### LineBot.on(eventType, eventHandler) 96 | 97 | Raised when a [Webhook event][webhook-event-url] is received. 98 | ```js 99 | bot.on('message', function (event) { }); 100 | bot.on('follow', function (event) { }); 101 | bot.on('unfollow', function (event) { }); 102 | bot.on('join', function (event) { }); 103 | bot.on('leave', function (event) { }); 104 | bot.on('memberJoined', function (event) { }); 105 | bot.on('memberLeft', function (event) { }); 106 | bot.on('postback', function (event) { }); 107 | bot.on('beacon', function (event) { }); 108 | ``` 109 | 110 | ### LineBot.reply(replyToken, message) 111 | 112 | Reply a message. 113 | 114 | See: [Event.reply(message)](#eventreplymessage) 115 | 116 | ### LineBot.push(to, message) 117 | 118 | Send push message. 119 | 120 | `to` is a userId, or an array of userId. 121 | A userId can be saved from `event.source.userId` 122 | when added as a friend (follow event), or during the chat (message event). 123 | 124 | `message` can be a string, an array of string, 125 | a [Send message][send-message-url] object, 126 | or an array of [Send message][send-message-url] objects. 127 | 128 | ### LineBot.multicast(to, message) 129 | 130 | Send push message to multiple users (Max: 150 users). 131 | This is more efficient than `push` as it will make api call only once. 132 | 133 | `to` is an array of userId. 134 | 135 | `message` can be a string, an array of string, 136 | a [Send message][send-message-url] object, 137 | or an array of [Send message][send-message-url] objects. 138 | 139 | ### LineBot.broadcast(message) 140 | 141 | Send push message to all users. 142 | This is more efficient than `push` as it will make api call only once. 143 | 144 | `message` can be a string, an array of string, 145 | a [Send message][send-message-url] object, 146 | or an array of [Send message][send-message-url] objects. 147 | 148 | ### LineBot.getMessageContent(messageId) 149 | 150 | Get image, video, and audio data sent by users as a [Buffer][buffer-url] object. 151 | 152 | See: [Event.message.content()](#eventmessagecontent) 153 | 154 | ### LineBot.getUserProfile(userId) 155 | 156 | Get user profile information of the user. 157 | 158 | See: [Event.source.profile()](#eventsourceprofile) 159 | 160 | ### LineBot.getGroupMemberProfile(groupId, userId) 161 | 162 | Get user profile of a member in a group. 163 | 164 | ### LineBot.getGroupMember(groupId) 165 | 166 | Get userId of all members in a group. 167 | 168 | See: [Event.source.member()](#eventsourcemember) 169 | 170 | ### LineBot.leaveGroup(groupId) 171 | 172 | Leave a group. 173 | 174 | ### LineBot.getRoomMemberProfile(roomId, userId) 175 | 176 | Get user profile of a member in a chat room. 177 | 178 | ### LineBot.getRoomMember(roomId) 179 | 180 | Get userId of all members in a chat room. 181 | 182 | See: [Event.source.member()](#eventsourcemember) 183 | 184 | ### LineBot.leaveRoom(roomId) 185 | 186 | Leave a room. 187 | 188 | ### LineBot.getTotalFollowers(date) 189 | 190 | Get the number of users who have added this linebot on or before a specified date. 191 | 192 | Default date is yesterday (UTC+9). 193 | 194 | See: [Get number of followers](https://developers.line.biz/en/reference/messaging-api/#get-number-of-followers) 195 | 196 | ### LineBot.getQuota() 197 | 198 | Get the number of messages quota in the current month. 199 | 200 | See: [Get the target limit for additional messages](https://developers.line.biz/en/reference/messaging-api/#get-quota) 201 | 202 | ### LineBot.getTotalReplyMessages(date) 203 | ### LineBot.getTotalPushMessages(date) 204 | ### LineBot.getTotalBroadcastMessages(date) 205 | ### LineBot.getTotalMulticastMessages(date) 206 | 207 | Get the number of messages that this linebot reply, push, broadcast, or multicast. 208 | 209 | Default date is yesterday (UTC+9). 210 | 211 | See: [Get number of sent reply messages](https://developers.line.biz/en/reference/messaging-api/#get-number-of-reply-messages) 212 | 213 | ## Event object 214 | 215 | Provide convenient shorthands to call LineBot's functions 216 | which require parameter from a source event object. 217 | 218 | ### Event.reply(message) 219 | 220 | Respond to the event. 221 | 222 | `message` can be a string, an array of string, 223 | a [Send message][send-message-url] object, 224 | or an array of [Send message][send-message-url] objects. 225 | 226 | Return a [Promise][promise-url] object from [`node-fetch`][node-fetch-url] module. 227 | 228 | This is a shorthand for: `LineBot.reply(event.replyToken, message)` 229 | 230 | ```js 231 | // reply text message 232 | event.reply('Hello, world').then(function (data) { 233 | // success 234 | }).catch(function (error) { 235 | // error 236 | }); 237 | 238 | // reply multiple text messages 239 | event.reply(['Hello, world 1', 'Hello, world 2']); 240 | 241 | // reply message object 242 | event.reply({ type: 'text', text: 'Hello, world' }); 243 | 244 | // reply multiple message object 245 | event.reply([ 246 | { type: 'text', text: 'Hello, world 1' }, 247 | { type: 'text', text: 'Hello, world 2' } 248 | ]); 249 | 250 | event.reply({ 251 | type: 'image', 252 | originalContentUrl: 'https://example.com/original.jpg', 253 | previewImageUrl: 'https://example.com/preview.jpg' 254 | }); 255 | 256 | event.reply({ 257 | type: 'video', 258 | originalContentUrl: 'https://example.com/original.mp4', 259 | previewImageUrl: 'https://example.com/preview.jpg' 260 | }); 261 | 262 | event.reply({ 263 | type: 'audio', 264 | originalContentUrl: 'https://example.com/original.m4a', 265 | duration: 240000 266 | }); 267 | 268 | event.reply({ 269 | type: 'location', 270 | title: 'my location', 271 | address: '〒150-0002 東京都渋谷区渋谷2丁目21−1', 272 | latitude: 35.65910807942215, 273 | longitude: 139.70372892916203 274 | }); 275 | 276 | event.reply({ 277 | type: 'sticker', 278 | packageId: '1', 279 | stickerId: '1' 280 | }); 281 | 282 | event.reply({ 283 | type: 'imagemap', 284 | baseUrl: 'https://example.com/bot/images/rm001', 285 | altText: 'this is an imagemap', 286 | baseSize: { height: 1040, width: 1040 }, 287 | actions: [{ 288 | type: 'uri', 289 | linkUri: 'https://example.com/', 290 | area: { x: 0, y: 0, width: 520, height: 1040 } 291 | }, { 292 | type: 'message', 293 | text: 'hello', 294 | area: { x: 520, y: 0, width: 520, height: 1040 } 295 | }] 296 | }); 297 | 298 | event.reply({ 299 | type: 'template', 300 | altText: 'this is a buttons template', 301 | template: { 302 | type: 'buttons', 303 | thumbnailImageUrl: 'https://example.com/bot/images/image.jpg', 304 | title: 'Menu', 305 | text: 'Please select', 306 | actions: [{ 307 | type: 'postback', 308 | label: 'Buy', 309 | data: 'action=buy&itemid=123' 310 | }, { 311 | type: 'postback', 312 | label: 'Add to cart', 313 | data: 'action=add&itemid=123' 314 | }, { 315 | type: 'uri', 316 | label: 'View detail', 317 | uri: 'http://example.com/page/123' 318 | }] 319 | } 320 | }); 321 | 322 | event.reply({ 323 | type: 'template', 324 | altText: 'this is a confirm template', 325 | template: { 326 | type: 'confirm', 327 | text: 'Are you sure?', 328 | actions: [{ 329 | type: 'message', 330 | label: 'Yes', 331 | text: 'yes' 332 | }, { 333 | type: 'message', 334 | label: 'No', 335 | text: 'no' 336 | }] 337 | } 338 | }); 339 | 340 | event.reply({ 341 | type: 'template', 342 | altText: 'this is a carousel template', 343 | template: { 344 | type: 'carousel', 345 | columns: [{ 346 | thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg', 347 | title: 'this is menu', 348 | text: 'description', 349 | actions: [{ 350 | type: 'postback', 351 | label: 'Buy', 352 | data: 'action=buy&itemid=111' 353 | }, { 354 | type: 'postback', 355 | label: 'Add to cart', 356 | data: 'action=add&itemid=111' 357 | }, { 358 | type: 'uri', 359 | label: 'View detail', 360 | uri: 'http://example.com/page/111' 361 | }] 362 | }, { 363 | thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg', 364 | title: 'this is menu', 365 | text: 'description', 366 | actions: [{ 367 | type: 'postback', 368 | label: 'Buy', 369 | data: 'action=buy&itemid=222' 370 | }, { 371 | type: 'postback', 372 | label: 'Add to cart', 373 | data: 'action=add&itemid=222' 374 | }, { 375 | type: 'uri', 376 | label: 'View detail', 377 | uri: 'http://example.com/page/222' 378 | }] 379 | }] 380 | } 381 | }); 382 | ``` 383 | 384 | ### Event.source.profile() 385 | 386 | Get user profile information of the sender. 387 | 388 | This is a shorthand for: 389 | - `LineBot.getUserProfile(event.source.userId)` if it is 1:1 chat 390 | - `LineBot.getGroupMemberProfile(event.source.groupId, event.source.userId)` if bot is in a group 391 | - `LineBot.getRoomMemberProfile(event.source.roomId, event.source.userId)` if bot is in a chat room 392 | 393 | ```js 394 | event.source.profile().then(function (profile) { 395 | event.reply('Hello ' + profile.displayName); 396 | }); 397 | ``` 398 | 399 | ### Event.source.member() 400 | 401 | Get userId of all members in a group or a chat room. 402 | 403 | This is a shorthand for: 404 | - `LineBot.getGroupMember(event.source.groupId)` if bot is in a group 405 | - `LineBot.getRoomMember(event.source.roomId)` if bot is in a chat room 406 | 407 | ```js 408 | event.source.member().then(function (member) { 409 | console.log(member.memberIds); 410 | }); 411 | ``` 412 | 413 | ### Event.message.content() 414 | 415 | Get image, video, and audio data sent by users as a [Buffer][buffer-url] object. 416 | 417 | This is a shorthand for: `LineBot.getMessageContent(event.message.messageId)` 418 | 419 | ```js 420 | event.message.content().then(function (content) { 421 | console.log(content.toString('base64')); 422 | }); 423 | ``` 424 | 425 | # License 426 | 427 | [MIT](LICENSE) 428 | 429 | [express-url]: http://expressjs.com 430 | [webhook-event-url]: https://developers.line.biz/en/reference/messaging-api/#webhooks 431 | [send-message-url]: https://developers.line.biz/en/reference/messaging-api/#message-objects 432 | [promise-url]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise 433 | [node-fetch-url]: https://github.com/bitinn/node-fetch 434 | [buffer-url]: https://nodejs.org/api/buffer.html 435 | 436 | [npm-image]: https://img.shields.io/npm/v/linebot.svg 437 | [npm-url]: https://npmjs.org/package/linebot 438 | [dependencies-image]: https://david-dm.org/boybundit/linebot.svg 439 | [dependencies-url]: https://david-dm.org/boybundit/linebot 440 | [downloads-image]: https://img.shields.io/npm/dm/linebot.svg 441 | [downloads-url]: https://npmjs.org/package/linebot 442 | [travis-image]: https://img.shields.io/travis/boybundit/linebot/master.svg 443 | [travis-url]: https://travis-ci.org/boybundit/linebot 444 | --------------------------------------------------------------------------------