├── .gitignore ├── LICENSE ├── README.md ├── examples ├── EchoBot │ ├── Procfile │ ├── README.md │ ├── app.json │ ├── index.js │ └── package.json └── Simsimi │ ├── Procfile │ ├── README.md │ ├── app.json │ ├── index.js │ └── package.json ├── index.js ├── lib ├── SignatureValidator.js ├── client.js ├── constants │ ├── BotAPIChannel.js │ ├── ContentType.js │ ├── Endpoint.js │ ├── EventType.js │ ├── OpType.js │ └── RecipientType.js ├── exceptions │ ├── ContentsDownloadingFailedException.js │ ├── IllegalRichMessageHeightException.js │ ├── InvalidSignatureException.js │ ├── JSONDecodingException.js │ ├── JSONEncodingException.js │ ├── LINEBotAPIException.js │ ├── UnsupportedContentTypeException.js │ ├── UnsupportedEventTypeException.js │ └── UnsupportedOperationTypeException.js ├── message │ ├── Markup.js │ ├── MultipleMessages.js │ ├── messageBuilder.js │ └── multipleMessagesBuilder.js └── receive │ ├── Message.js │ ├── Operation.js │ ├── Receive.js │ ├── ReceiveFactory.js │ ├── message │ ├── Audio.js │ ├── Contact.js │ ├── Image.js │ ├── Location.js │ ├── MessageReceiveFactory.js │ ├── Sticker.js │ ├── Text.js │ └── Video.js │ └── operation │ ├── AddContact.js │ ├── AddedToRoom.js │ ├── BlockContact.js │ ├── InvitedToGroup.js │ └── OperationReceiveFactory.js ├── package.json └── test ├── testReceiveFactory.js ├── testSendMessage.js └── testSignatureValidator.js /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Runnables Company Limited 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with 4 | or without fee is hereby granted, provided that the above copyright notice and this 5 | permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 8 | TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 9 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 10 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 11 | IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 12 | CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | line-bot-sdk-nodejs 2 | == 3 | 4 | [![npm version](https://badge.fury.io/js/line-bot-sdk.svg)](https://badge.fury.io/js/line-bot-sdk) 5 | [![dependencies](https://david-dm.org/runnables/line-bot-sdk-nodejs.svg)](https://david-dm.org/runnables/line-bot-sdk-nodejs) 6 | 7 | SDK of the LINE BOT API for Node.js 8 | 9 | Installation 10 | -- 11 | 12 | The LINE BOT API SDK can be installed with [NPM](https://www.npmjs.com). 13 | 14 | ``` 15 | $ npm install line-bot-sdk 16 | ``` 17 | 18 | Usage 19 | -- 20 | 21 | ### Require SDK 22 | 23 | ```js 24 | var LineBot = require('line-bot-sdk'); 25 | ``` 26 | 27 | ### Bot API Trial 28 | 29 | ```js 30 | var client = LineBot.client({ 31 | channelID: '', 32 | channelSecret: '', 33 | channelMID: '' 34 | }); 35 | ``` 36 | 37 | ### Business Connect 38 | 39 | ```js 40 | var client = LineBot.client({ 41 | channelID: '', 42 | channelSecret: '', 43 | channelToken: '' 44 | }); 45 | ``` 46 | 47 | ### Enable Debugging Mode 48 | 49 | Use `DEBUG` env var to enable debugging mode 50 | 51 | Available values 52 | * line-bot:log 53 | * line-bot:error 54 | 55 | ``` 56 | $ DEBUG=line-bot:* node app.js 57 | ``` 58 | 59 | ### Sending Message 60 | 61 | Note: mid can be either a string or an array of strings. 62 | 63 | #### sendText(mid, text[, toType]) 64 | 65 | Send a text message to mid(s). 66 | [https://developers.line.me/bot-api/api-reference#sending_message_text](https://developers.line.me/bot-api/api-reference#sending_message_text) 67 | 68 | ```js 69 | client.sendText('', 'Message'); 70 | client.sendText(['', ''], 'Message'); 71 | ``` 72 | 73 | #### sendImage(mid, imageURL, previewURL[, toType]) 74 | 75 | Send an image to mid(s). 76 | [https://developers.line.me/bot-api/api-reference#sending_message_image](https://developers.line.me/bot-api/api-reference#sending_message_image) 77 | 78 | ```js 79 | client.sendImage('', 'http://example.com/image.jpg', 'http://example.com/preview.jpg'); 80 | ``` 81 | 82 | #### sendVideo(mid, videoURL, previewImageURL[, toType]) 83 | 84 | Send a video to mid(s). 85 | [https://developers.line.me/bot-api/api-reference#sending_message_video](https://developers.line.me/bot-api/api-reference#sending_message_video) 86 | 87 | ```js 88 | client.sendVideo('', 'http://example.com/video.mp4', 'http://example.com/video_preview.jpg'); 89 | ``` 90 | 91 | #### sendAudio(mid, audioURL, durationMillis[, toType]) 92 | 93 | Send a voice message to mid(s). 94 | [https://developers.line.me/bot-api/api-reference#sending_message_audio](https://developers.line.me/bot-api/api-reference#sending_message_audio) 95 | 96 | ```js 97 | client.sendAudio('', 'http://example.com/audio.m4a', 5000); 98 | ``` 99 | 100 | #### sendLocation(mid, text, latitude, longitude[, toType]) 101 | 102 | Send location information to mid(s). 103 | [https://developers.line.me/bot-api/api-reference#sending_message_location](https://developers.line.me/bot-api/api-reference#sending_message_location) 104 | 105 | ```js 106 | client.sendLocation('', '2 Chome-21-1 Shibuya Tokyo 150-0002, Japan', 35.658240, 139.703478); 107 | ``` 108 | 109 | #### sendSticker(mid, stkid, stkpkgid, stkver[, toType]) 110 | 111 | Send a sticker to mid(s). 112 | [https://developers.line.me/bot-api/api-reference#sending_message_sticker](https://developers.line.me/bot-api/api-reference#sending_message_sticker) 113 | 114 | ```js 115 | client.sendSticker('', 1, 1, 100); 116 | ``` 117 | 118 | #### sendRichMessage(mid, imageURL, altText, markup[, toType]) 119 | 120 | Send a rich message to mid(s). 121 | [https://developers.line.me/bot-api/api-reference#sending_rich_content_message_request](https://developers.line.me/bot-api/api-reference#sending_rich_content_message_request) 122 | 123 | Note: Please see [image url specifications](https://developers.line.me/bot-api/api-reference#sending_rich_content_message_prerequisite) 124 | 125 | ```js 126 | var Markup = LineBot.Markup; 127 | var markup = new Markup(1040); // height 128 | 129 | markup 130 | .setAction('openHomepage', 'Open Homepage', 'https://line.me') 131 | .addListener('openHomepage', 0, 0, 1040, 1040); 132 | 133 | client.sendRichMessage('', 'https://example.com/image', 'Alt text', markup.build()); 134 | ``` 135 | 136 | #### sendMultipleMessages(mid, multipleMessages[, messageNotified]) 137 | 138 | Send multiple messages to mids(s). 139 | [https://developers.line.me/bot-api/api-reference#sending_multiple_messages_request](https://developers.line.me/bot-api/api-reference#sending_multiple_messages_request) 140 | 141 | ```js 142 | var MultipleMessages = LineBot.MultipleMessages; 143 | var Markup = LineBot.Markup; 144 | var multipleMessages = new MultipleMessages(); 145 | var markup = new Markup(1040); // height 146 | 147 | // markup for rich message 148 | markup 149 | .setAction('openHomepage', 'Open Homepage', 'https://line.me') 150 | .addListener('openHomepage', 0, 0, 1040, 1040); 151 | 152 | multipleMessages 153 | .addText('Text') 154 | .addImage('http://example.com/image.jpg', 'http://example.com/preview.jpg') 155 | .addVideo('http://example.com/video.mp4', 'http://example.com/video_preview.jpg') 156 | .addAudio('http://example.com/audio.m4a', 5000) 157 | .addLocation('2 Chome-21-1 Shibuya Tokyo 150-0002, Japan', 35.658240, 139.703478) 158 | .addSticker(1, 1, 100) 159 | .addRichMessage('https://example.com/image', 'Alt text', markup.build()); 160 | 161 | client.sendMultipleMessages('', multipleMessages); 162 | ``` 163 | 164 | ### Getting User Profile Information 165 | 166 | #### getUserProfile(mid) 167 | 168 | Retrieve user profile(s) that is associated with mid(s). 169 | [https://developers.line.me/bot-api/api-reference#getting_user_profile_information_request](https://developers.line.me/bot-api/api-reference#getting_user_profile_information_request) 170 | 171 | Note: mid can be either a string or an array of strings. 172 | 173 | ```js 174 | client 175 | .getUserProfile(['']) 176 | .then(function(res) { 177 | var contacts = res.body.contacts; 178 | 179 | client.sendText(contacts[0].mid, 'Hello, ' + contacts[0].displayName); 180 | }); 181 | ``` 182 | 183 | ### Other 184 | 185 | #### validateSignature(rawJSON, signature) 186 | 187 | Validate signature. 188 | 189 | ```js 190 | var isValid = client.validateSignature(requestJSON, ''); 191 | ``` 192 | 193 | #### createReceivesFromJSON(json) 194 | 195 | ```js 196 | var receives = client.createReceivesFromJSON(requestBody); 197 | ``` 198 | 199 | Test 200 | -- 201 | 202 | ``` 203 | $ npm test 204 | ``` 205 | 206 | License 207 | -- 208 | 209 | ``` 210 | Copyright (c) 2016, Runnables Company Limited 211 | 212 | Permission to use, copy, modify, and/or distribute this software for any purpose with 213 | or without fee is hereby granted, provided that the above copyright notice and this 214 | permission notice appear in all copies. 215 | 216 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 217 | TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. 218 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 219 | DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 220 | IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 221 | CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 222 | ``` 223 | 224 | See Also 225 | -- 226 | 227 | - [https://business.line.me/](https://business.line.me/) 228 | - [https://developers.line.me/bot-api/overview](https://developers.line.me/bot-api/overview) 229 | - [https://developers.line.me/bot-api/getting-started-with-bot-api-trial](https://developers.line.me/bot-api/getting-started-with-bot-api-trial) 230 | -------------------------------------------------------------------------------- /examples/EchoBot/Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /examples/EchoBot/README.md: -------------------------------------------------------------------------------- 1 | # LINE Bot API EchoBot Example 2 | 3 | A LINE Bot API example showing how to receive and return each type of messages. 4 | 5 | ## Prerequisite 6 | - Have a [LINE BOT Trial](https://developers.line.me/bot-api/overview) account running 7 | 8 | ## Running Locally 9 | 10 | Set your credentials for LINE Bot API and Simsimi API in index.js 11 | Make sure you have [Node.js](http://nodejs.org/) installed. 12 | 13 | ```sh 14 | $ npm install 15 | $ npm start 16 | ``` 17 | 18 | Your app should now be running on [localhost:5000](http://localhost:5000/). 19 | 20 | ## Deploying to Heroku 21 | 22 | ``` 23 | $ git init 24 | $ heroku create 25 | $ git push heroku master 26 | ``` 27 | -------------------------------------------------------------------------------- /examples/EchoBot/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LINE Bot integration with Simsimi example", 3 | "description": "A LINE Bot server NodeJS example that shows how to use Simsimi to respond users", 4 | "repository": "", 5 | "logo": "http://node-js-sample.herokuapp.com/node.svg", 6 | "keywords": ["line", "linebot", "simsimi"], 7 | "image": "heroku/nodejs" 8 | } 9 | -------------------------------------------------------------------------------- /examples/EchoBot/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var bodyParser = require('body-parser'); 3 | var express = require('express'); 4 | var request = require('superagent'); 5 | var LineBot = require('line-bot-sdk'); 6 | var client = LineBot.client({ 7 | channelID: 'YOUR_CHANNEL_ID', 8 | channelSecret: 'YOUR_CHANNEL_SECRET', 9 | channelMID: 'YOUR_CHANNEL_MID' 10 | }); 11 | 12 | var app = express(); 13 | 14 | app.set('port', (process.env.PORT || 5000)); 15 | 16 | app.use(bodyParser.urlencoded({ extended: false, limit: 2 * 1024 * 1024 })); 17 | app.use(bodyParser.json({ limit: 2 * 1024 * 1024 })); 18 | 19 | app.post('/', function (req, res) { 20 | console.log(req.body.result); 21 | 22 | var receives = client.createReceivesFromJSON(req.body); 23 | _.each(receives, function(receive){ 24 | 25 | if(receive.isMessage()){ 26 | 27 | if(receive.isText()){ 28 | 29 | if(receive.getText()==='me'){ 30 | client.getUserProfile(receive.getFromMid()) 31 | .then(function onResult(res){ 32 | if(res.status === 200){ 33 | var contacts = res.body.contacts; 34 | if(contacts.length > 0){ 35 | client.sendText(receive.getFromMid(), 'Hi!, you\'re ' + contacts[0].displayName); 36 | } 37 | } 38 | }, function onError(err){ 39 | console.error(err); 40 | }); 41 | } else { 42 | client.sendText(receive.getFromMid(), receive.getText()); 43 | } 44 | 45 | }else if(receive.isImage()){ 46 | 47 | client.sendText(receive.getFromMid(), 'Thanks for the image!'); 48 | 49 | }else if(receive.isVideo()){ 50 | 51 | client.sendText(receive.getFromMid(), 'Thanks for the video!'); 52 | 53 | }else if(receive.isAudio()){ 54 | 55 | client.sendText(receive.getFromMid(), 'Thanks for the audio!'); 56 | 57 | }else if(receive.isLocation()){ 58 | 59 | client.sendLocation( 60 | receive.getFromMid(), 61 | receive.getText() + receive.getAddress(), 62 | receive.getLatitude(), 63 | receive.getLongitude() 64 | ); 65 | 66 | }else if(receive.isSticker()){ 67 | 68 | // This only works if the BOT account have the same sticker too 69 | client.sendSticker( 70 | receive.getFromMid(), 71 | receive.getStkId(), 72 | receive.getStkPkgId(), 73 | receive.getStkVer() 74 | ); 75 | 76 | }else if(receive.isContact()){ 77 | 78 | client.sendText(receive.getFromMid(), 'Thanks for the contact'); 79 | 80 | }else{ 81 | console.error('found unknown message type'); 82 | } 83 | }else if(receive.isOperation()){ 84 | 85 | console.log('found operation'); 86 | 87 | }else { 88 | 89 | console.error('invalid receive type'); 90 | 91 | } 92 | 93 | }); 94 | 95 | res.send('ok'); 96 | }); 97 | 98 | app.listen(app.get('port'), function () { 99 | console.log('Listening on port ' + app.get('port')); 100 | }); -------------------------------------------------------------------------------- /examples/EchoBot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-bat-sdk-nodejs-example-echobot", 3 | "version": "0.2.5", 4 | "description": "A sample Node.js app using Express 4", 5 | "engines": { 6 | "node": "5.9.1" 7 | }, 8 | "main": "index.js", 9 | "scripts": { 10 | "start": "node index.js" 11 | }, 12 | "dependencies": { 13 | "body-parser": "^1.15.1", 14 | "ejs": "2.4.1", 15 | "express": "4.13.3", 16 | "line-bot-sdk": "*", 17 | "lodash": "^4.12.0", 18 | "superagent": "^1.8.3" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/heroku/node-js-getting-started" 23 | }, 24 | "keywords": [ 25 | "node", 26 | "heroku", 27 | "express" 28 | ], 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /examples/Simsimi/Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /examples/Simsimi/README.md: -------------------------------------------------------------------------------- 1 | # LINE Bot API Simsimi Example 2 | 3 | A LINE Bot API example showing how to redirect your communication with LINE bot to Simsimi API and then back. 4 | 5 | ## Prerequisite 6 | - Have a [LINE BOT Trial](https://developers.line.me/bot-api/overview) account running 7 | - Have an access to [Simsimi API](http://developer.simsimi.com/api) (Trial version is fine) 8 | 9 | ## Running Locally 10 | 11 | Set your credentials for LINE Bot API and Simsimi API in index.js 12 | Make sure you have [Node.js](http://nodejs.org/) installed. 13 | 14 | ```sh 15 | $ npm install 16 | $ npm start 17 | ``` 18 | 19 | Your app should now be running on [localhost:5000](http://localhost:5000/). 20 | 21 | ## Deploying to Heroku 22 | 23 | ``` 24 | $ git init 25 | $ heroku create 26 | $ git push heroku master 27 | ``` 28 | -------------------------------------------------------------------------------- /examples/Simsimi/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LINE Bot integration with Simsimi example", 3 | "description": "A LINE Bot server NodeJS example that shows how to use Simsimi to respond users", 4 | "repository": "", 5 | "logo": "http://node-js-sample.herokuapp.com/node.svg", 6 | "keywords": ["line", "linebot", "simsimi"], 7 | "image": "heroku/nodejs" 8 | } 9 | -------------------------------------------------------------------------------- /examples/Simsimi/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var bodyParser = require('body-parser'); 3 | var express = require('express'); 4 | var request = require('superagent'); 5 | var LineBot = require('line-bot-sdk'); 6 | var client = LineBot.client({ 7 | channelID: 'YOUR_CHANNEL_ID', 8 | channelSecret: 'YOUR_CHANNEL_SECRET', 9 | channelMID: 'YOUR_CHANNEL_MID' 10 | }); 11 | 12 | var simsimiAPIKey = 'YOUR_SIMSIMI_API_KEY'; 13 | var simsimiLanguageCode = 'th'; 14 | var app = express(); 15 | 16 | app.set('port', (process.env.PORT || 5000)); 17 | 18 | app.use(bodyParser.urlencoded({ extended: false, limit: 2 * 1024 * 1024 })); 19 | app.use(bodyParser.json({ limit: 2 * 1024 * 1024 })); 20 | 21 | app.post('/', function (req, res) { 22 | console.log(req.body); 23 | 24 | if (req.body.result && _.isArray(req.body.result)) { 25 | _.each(req.body.result, function(item) { 26 | if (item.content && item.content.from && item.content.text) { 27 | request 28 | .get('http://sandbox.api.simsimi.com/request.p?key=' + simsimiAPIKey + 29 | '&text=' + encodeURIComponent(item.content.text) + 30 | '&lc=' + simsimiLanguageCode) 31 | .end(function(err, res){ 32 | if(!err){ 33 | client.sendText([item.content.from], res.body.response); 34 | } 35 | }); 36 | // 37 | } 38 | }); 39 | } 40 | 41 | res.send('ok'); 42 | }); 43 | 44 | app.listen(app.get('port'), function () { 45 | console.log('Listening on port ' + app.get('port')); 46 | }); -------------------------------------------------------------------------------- /examples/Simsimi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-js-getting-started", 3 | "version": "0.2.5", 4 | "description": "A sample Node.js app using Express 4", 5 | "engines": { 6 | "node": "5.9.1" 7 | }, 8 | "main": "index.js", 9 | "scripts": { 10 | "start": "node index.js" 11 | }, 12 | "dependencies": { 13 | "body-parser": "^1.15.1", 14 | "ejs": "2.4.1", 15 | "express": "4.13.3", 16 | "line-bot-sdk": "*", 17 | "lodash": "^4.12.0", 18 | "superagent": "^1.8.3" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/heroku/node-js-getting-started" 23 | }, 24 | "keywords": [ 25 | "node", 26 | "heroku", 27 | "express" 28 | ], 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | client: function(config) { 3 | return require('./lib/client').init(config); 4 | }, 5 | ContentType: require('./lib/constants/ContentType'), 6 | EventType: require('./lib/constants/EventType'), 7 | OpType: require('./lib/constants/OpType'), 8 | RecipientType: require('./lib/constants/RecipientType'), 9 | Markup: require('./lib/message/Markup'), 10 | MultipleMessages: require('./lib/message/MultipleMessages') 11 | }; 12 | -------------------------------------------------------------------------------- /lib/SignatureValidator.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var InvalidSignatureException = require('./exceptions/InvalidSignatureException'); 3 | 4 | exports.validateSignature = function(rawJSON, channelSecret, signature) { 5 | if (!signature) { 6 | throw new InvalidSignatureException('Signature must not be empty'); 7 | } 8 | return signature === crypto.createHmac('SHA256', channelSecret).update(rawJSON).digest('base64'); 9 | }; 10 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug'); 2 | var error = debug('line-bot:error'); 3 | var log = debug('line-bot:log'); 4 | var request = require('superagent-promise')(require('superagent'), Promise); 5 | var merge = require('lodash.merge'); 6 | 7 | var ReceiveFactory = require('./receive/ReceiveFactory'); 8 | var SignatureValidator = require('./SignatureValidator'); 9 | 10 | var BotAPIChannel = require('./constants/BotAPIChannel'); 11 | var Endpoint = require('./constants/Endpoint'); 12 | var EventType = require('./constants/EventType'); 13 | var MessageBuilder = require('./message/messageBuilder'); 14 | var MultipleMessagesBuilder = require('./message/multipleMessagesBuilder'); 15 | var RecipientType = require('./constants/RecipientType'); 16 | 17 | module.exports.init = function(config) { 18 | var api = {}; 19 | var defaultHeaders = { 'Content-Type': 'application/json; charset=UTF-8' }; 20 | 21 | defaultHeaders['X-Line-ChannelID'] = config.channelID || ''; 22 | defaultHeaders['X-Line-ChannelSecret'] = config.channelSecret || ''; 23 | defaultHeaders['X-Line-Trusted-User-With-ACL'] = config.channelMID || ''; 24 | defaultHeaders['X-LINE-ChannelToken'] = config.channelToken || ''; 25 | 26 | api.sendText = function(mid, text, toType) { 27 | return this.sendMessage(mid, MessageBuilder.buildText(text), toType || RecipientType.USER); 28 | }; 29 | 30 | api.sendImage = function(mid, imageUrl, previewUrl, toType) { 31 | return this.sendMessage(mid, MessageBuilder.buildImage(imageUrl, previewUrl), toType || RecipientType.USER); 32 | }; 33 | 34 | api.sendVideo = function(mid, videoUrl, previewImageUrl, toType) { 35 | return this.sendMessage(mid, MessageBuilder.buildVideo(videoUrl, previewImageUrl), toType || RecipientType.USER); 36 | }; 37 | 38 | api.sendAudio = function(mid, audioUrl, durationMillis, toType) { 39 | return this.sendMessage(mid, MessageBuilder.buildAudio(audioUrl, durationMillis), toType || RecipientType.USER); 40 | }; 41 | 42 | api.sendLocation = function(mid, text, latitude, longitude, toType) { 43 | return this.sendMessage(mid, MessageBuilder.buildLocation(text, latitude, longitude), toType || RecipientType.USER); 44 | }; 45 | 46 | api.sendSticker = function(mid, stkid, stkpkgid, stkver, toType) { 47 | return this.sendMessage(mid, MessageBuilder.buildSticker(stkid, stkpkgid, stkver || null), toType || RecipientType.USER); 48 | }; 49 | 50 | api.sendRichMessage = function(mid, imageUrl, altText, markup, toType) { 51 | return this.sendMessage(mid, MessageBuilder.buildRichMessage(imageUrl, altText, markup), toType || RecipientType.USER); 52 | }; 53 | 54 | // Not supported for groups and rooms. 55 | api.sendMultipleMessages = function(mid, multipleMessages, messageNotified) { 56 | return this.sendMessage(mid, MultipleMessagesBuilder.buildMultipleMessages(multipleMessages, messageNotified || 0), null, EventType.SENDING_MULTIPLE_MESSAGES); 57 | }; 58 | 59 | api.getMessageContent = function(messageId, fileHandler) { 60 | 61 | }; 62 | 63 | api.getMessageContentPreview = function(messageId, fileHandler) { 64 | 65 | }; 66 | 67 | api.getUserProfile = function(mid) { 68 | return request.get(Endpoint.PROFILE) 69 | .set(defaultHeaders) 70 | .query({ mids: mid instanceof Array ? mid.join() : mid }) 71 | .end(); 72 | }; 73 | 74 | api.validateSignature = function(rawJSON, signature) { 75 | return SignatureValidator.validateSignature(rawJSON, config.channelSecret, signature); 76 | }; 77 | 78 | api.createReceivesFromJSON = function(json) { 79 | return ReceiveFactory.createFromJSON(config, json); 80 | }; 81 | 82 | api.sendMessage = function(mid, data, toType, eventType) { 83 | return this.postMessage({ 84 | to: mid instanceof Array ? mid: [mid], 85 | content: merge(data, { toType: toType || RecipientType.USER }) 86 | }, eventType || EventType.SENDING_MESSAGE); 87 | }; 88 | 89 | api.postMessage = function(data, eventType) { 90 | function getEndpoint() { 91 | return config.channelToken ? Endpoint.BC_EVENT : Endpoint.EVENT; 92 | } 93 | 94 | data.toChannel = BotAPIChannel.SENDING_CHANNEL_ID; 95 | data.eventType = eventType; 96 | 97 | // debugging 98 | log('POST ' + getEndpoint()); 99 | log('headers:'); 100 | log(defaultHeaders); 101 | log('body:'); 102 | log(data); 103 | 104 | return request.post(getEndpoint()) 105 | .set(defaultHeaders) 106 | .send(data) 107 | .then(function(res) { 108 | return res; 109 | }, function(err) { 110 | var response = err.response; 111 | 112 | // debugging 113 | error('statusCode: ' + response.statusCode); 114 | error('body: '); 115 | error(response.body); 116 | 117 | throw err; 118 | }); 119 | }; 120 | 121 | // For Business Connect 122 | api.sendLinkMessage = function() { 123 | 124 | }; 125 | 126 | // For Business Connect 127 | api.leaveGroup = function(groupId) { 128 | if (!config.channelToken) throw new Error('Leave a group is only appear in Business Connect API'); 129 | return request.del(Endpoint.LEAVE_GROUP + groupId) 130 | .set(defaultHeaders) 131 | .then(function(res) { 132 | return res; 133 | }, function(err) { 134 | var response = err.response; 135 | 136 | // debugging 137 | error('statusCode: ' + response.statusCode); 138 | error('body: '); 139 | error(response.body); 140 | 141 | throw err; 142 | }); 143 | }; 144 | 145 | // For Business Connect 146 | api.leaveRoom = function(roomId) { 147 | if (!config.channelToken) throw new Error('Leave a group is only appear in Business Connect API'); 148 | return request.del(Endpoint.LEAVE_ROOM + groupId) 149 | .set(defaultHeaders) 150 | .then(function(res) { 151 | return res; 152 | }, function(err) { 153 | var response = err.response; 154 | 155 | // debugging 156 | error('statusCode: ' + response.statusCode); 157 | error('body: '); 158 | error(response.body); 159 | 160 | throw err; 161 | }); 162 | }; 163 | 164 | // For Business Connect 165 | api.addOfficialAccount = function(accessToken) { 166 | if (!config.channelToken) throw new Error('Add an official account to a user\'s friend list is only appear in Business Connect API'); 167 | return request.post(Endpoint.OFFICAL_ACCOUNT_CONTACTS) 168 | .set('X-Line-ChannelToken', accessToken) 169 | .then(function(res) { 170 | return res; 171 | }, function(err) { 172 | var response = err.response; 173 | 174 | // debugging 175 | error('statusCode: ' + response.statusCode); 176 | error('body: '); 177 | error(response.body); 178 | 179 | throw err; 180 | }); 181 | }; 182 | 183 | return api; 184 | }; 185 | -------------------------------------------------------------------------------- /lib/constants/BotAPIChannel.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | RECEIVING_CHANNEL_ID: '1341301815', 3 | RECEIVING_CHANNEL_MID: 'u206d25c2ea6bd87c17655609a1c37cb8', 4 | SENDING_CHANNEL_ID: '1383378250' 5 | }; 6 | -------------------------------------------------------------------------------- /lib/constants/ContentType.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | TEXT: 1, 3 | IMAGE: 2, 4 | VIDEO: 3, 5 | AUDIO: 4, 6 | LOCATION: 7, 7 | STICKER: 8, 8 | CONTACT: 10, 9 | RICH_MESSAGE: 12 10 | }; 11 | -------------------------------------------------------------------------------- /lib/constants/Endpoint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | EVENT: 'https://trialbot-api.line.me/v1/events', 3 | PROFILE: 'https://trialbot-api.line.me/v1/profiles', 4 | BC_EVENT: 'https://api.line.me/v1/events', 5 | BC_PROFILE: 'https://api.line.me/v1/profiles', 6 | LEAVE_GROUP: 'https://api.line.me/v1/bot/group/leave/', 7 | LEAVE_ROOM: 'https://api.line.me/v1/bot/room/leave/', 8 | OFFICAL_ACCOUNT_CONTACTS: 'https://api.line.me/v1/officialaccount/contacts' 9 | }; 10 | -------------------------------------------------------------------------------- /lib/constants/EventType.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | RECEIVING_MESSAGE: '138311609000106303', 3 | RECEIVING_OPERATION: '138311609100106403', 4 | SENDING_MESSAGE: '138311608800106203', 5 | SENDING_MULTIPLE_MESSAGES: '140177271400161403' 6 | }; 7 | -------------------------------------------------------------------------------- /lib/constants/OpType.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ADDED_AS_FRIEND: 4, 3 | INVITED_TO_GROUP: 5, // For Business Connect 4 | ADDED_TO_ROOM: 7, // For Business Connect 5 | BLOCKED: 8 6 | }; 7 | -------------------------------------------------------------------------------- /lib/constants/RecipientType.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | USER: 1, 3 | ROOM: 2, // For Business Connect 4 | GROUP: 3, // For Business Connect 5 | }; 6 | -------------------------------------------------------------------------------- /lib/exceptions/ContentsDownloadingFailedException.js: -------------------------------------------------------------------------------- 1 | module.exports = function ContentsDownloadingFailedException(message, extra) { 2 | Error.captureStackTrace(this, this.constructor); 3 | this.name = this.constructor.name; 4 | this.message = message; 5 | this.extra = extra; 6 | }; 7 | 8 | require('util').inherits(module.exports, Error); 9 | -------------------------------------------------------------------------------- /lib/exceptions/IllegalRichMessageHeightException.js: -------------------------------------------------------------------------------- 1 | module.exports = function IllegalRichMessageHeightException(message, extra) { 2 | Error.captureStackTrace(this, this.constructor); 3 | this.name = this.constructor.name; 4 | this.message = message; 5 | this.extra = extra; 6 | }; 7 | 8 | require('util').inherits(module.exports, Error); 9 | -------------------------------------------------------------------------------- /lib/exceptions/InvalidSignatureException.js: -------------------------------------------------------------------------------- 1 | module.exports = function InvalidSignatureException(message, extra) { 2 | Error.captureStackTrace(this, this.constructor); 3 | this.name = this.constructor.name; 4 | this.message = message; 5 | this.extra = extra; 6 | }; 7 | 8 | require('util').inherits(module.exports, Error); 9 | -------------------------------------------------------------------------------- /lib/exceptions/JSONDecodingException.js: -------------------------------------------------------------------------------- 1 | module.exports = function JSONDecodingException(message, extra) { 2 | Error.captureStackTrace(this, this.constructor); 3 | this.name = this.constructor.name; 4 | this.message = message; 5 | this.extra = extra; 6 | }; 7 | 8 | require('util').inherits(module.exports, Error); 9 | -------------------------------------------------------------------------------- /lib/exceptions/JSONEncodingException.js: -------------------------------------------------------------------------------- 1 | module.exports = function JSONEncodingException(message, extra) { 2 | Error.captureStackTrace(this, this.constructor); 3 | this.name = this.constructor.name; 4 | this.message = message; 5 | this.extra = extra; 6 | }; 7 | 8 | require('util').inherits(module.exports, Error); 9 | -------------------------------------------------------------------------------- /lib/exceptions/LINEBotAPIException.js: -------------------------------------------------------------------------------- 1 | module.exports = function LineBotAPIException(message, extra) { 2 | Error.captureStackTrace(this, this.constructor); 3 | this.name = this.constructor.name; 4 | this.message = message; 5 | this.extra = extra; 6 | }; 7 | 8 | require('util').inherits(module.exports, Error); 9 | -------------------------------------------------------------------------------- /lib/exceptions/UnsupportedContentTypeException.js: -------------------------------------------------------------------------------- 1 | module.exports = function UnsupportedContentTypeException(message, extra) { 2 | Error.captureStackTrace(this, this.constructor); 3 | this.name = this.constructor.name; 4 | this.message = message; 5 | this.extra = extra; 6 | }; 7 | 8 | require('util').inherits(module.exports, Error); 9 | -------------------------------------------------------------------------------- /lib/exceptions/UnsupportedEventTypeException.js: -------------------------------------------------------------------------------- 1 | module.exports = function UnsupportedEventTypeException(message, extra) { 2 | Error.captureStackTrace(this, this.constructor); 3 | this.name = this.constructor.name; 4 | this.message = message; 5 | this.extra = extra; 6 | }; 7 | 8 | require('util').inherits(module.exports, Error); 9 | -------------------------------------------------------------------------------- /lib/exceptions/UnsupportedOperationTypeException.js: -------------------------------------------------------------------------------- 1 | module.exports = function UnsupportedOperationTypeException(message, extra) { 2 | Error.captureStackTrace(this, this.constructor); 3 | this.name = this.constructor.name; 4 | this.message = message; 5 | this.extra = extra; 6 | }; 7 | 8 | require('util').inherits(module.exports, Error); 9 | -------------------------------------------------------------------------------- /lib/message/Markup.js: -------------------------------------------------------------------------------- 1 | function Markup(height) { 2 | if (height > 2080) throw 'Rich Message canvas\'s height should be less than or equals 2080px'; 3 | 4 | this.canvas = { 5 | height: height, 6 | width: 1040, 7 | initialScene: 'scene1' 8 | }; 9 | this.images = { 10 | image1: { 11 | x: 0, 12 | y: 0, 13 | w: 1040, 14 | h: height 15 | } 16 | }; 17 | this.actions = {}; 18 | this.scenes = { 19 | scene1: { 20 | draws: [{ 21 | image: 'image1', 22 | x: 0, 23 | y: 0, 24 | w: 1040, 25 | h: height 26 | }], 27 | listeners: [] 28 | } 29 | }; 30 | } 31 | 32 | Markup.prototype.setAction = function(actionName, text, linkUri, type) { 33 | var obj = { type: type || 'web' }; 34 | 35 | switch(type) { 36 | case 'sendMessage': 37 | obj.params = { text: text }; 38 | break; 39 | default: 40 | obj.text = text; 41 | obj.params = { linkUri: linkUri }; 42 | break; 43 | } 44 | 45 | this.actions[actionName] = obj; 46 | 47 | return this; 48 | }; 49 | 50 | Markup.prototype.addListener = function(actionName, x, y, width, height) { 51 | this.scenes.scene1.listeners.push({ 52 | type: 'touch', 53 | params: [x, y, width, height], 54 | action: actionName 55 | }); 56 | 57 | return this; 58 | }; 59 | 60 | Markup.prototype.build = function() { 61 | return JSON.stringify(this); 62 | }; 63 | 64 | module.exports = Markup; 65 | -------------------------------------------------------------------------------- /lib/message/MultipleMessages.js: -------------------------------------------------------------------------------- 1 | var MessageBuilder = require('./messageBuilder'); 2 | 3 | function MultipleMessages() { 4 | this.messages = []; 5 | } 6 | 7 | MultipleMessages.prototype.addText = function(text) { 8 | this.messages.push(MessageBuilder.buildText(text)); 9 | return this; 10 | }; 11 | 12 | MultipleMessages.prototype.addImage = function(imageUrl, previewUrl) { 13 | this.messages.push(MessageBuilder.buildImage(imageUrl, previewUrl)); 14 | return this; 15 | }; 16 | 17 | MultipleMessages.prototype.addVideo = function(videoUrl, previewImageUrl) { 18 | this.messages.push(MessageBuilder.buildVideo(videoUrl, previewImageUrl)); 19 | return this; 20 | }; 21 | 22 | MultipleMessages.prototype.addAudio = function(audioUrl, duration) { 23 | this.messages.push(MessageBuilder.buildAudio(audioUrl, duration)); 24 | return this; 25 | }; 26 | 27 | MultipleMessages.prototype.addLocation = function(text, latitude, longitude) { 28 | this.messages.push(MessageBuilder.buildLocation(text, latitude, longitude)); 29 | return this; 30 | }; 31 | 32 | MultipleMessages.prototype.addSticker = function(stkid, stkpkgid, stkver) { 33 | this.messages.push(MessageBuilder.buildSticker(stkid, stkpkgid, stkver)); 34 | return this; 35 | }; 36 | 37 | MultipleMessages.prototype.addRichMessage = function(imageUrl, altText, markup) { 38 | this.messages.push(MessageBuilder.buildRichMessage(imageUrl, altText, markup)); 39 | return this; 40 | }; 41 | 42 | MultipleMessages.prototype.getMessages = function() { 43 | return this.messages; 44 | }; 45 | 46 | module.exports = MultipleMessages; 47 | -------------------------------------------------------------------------------- /lib/message/messageBuilder.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../constants/ContentType'); 2 | 3 | exports.buildText = function(text) { 4 | return { 5 | contentType: ContentType.TEXT, 6 | text: text 7 | }; 8 | }; 9 | 10 | exports.buildImage = function(imageUrl, previewUrl) { 11 | return { 12 | contentType: ContentType.IMAGE, 13 | originalContentUrl: imageUrl, 14 | previewImageUrl: previewUrl 15 | }; 16 | }; 17 | 18 | exports.buildVideo = function(videoUrl, previewImageUrl) { 19 | return { 20 | contentType: ContentType.VIDEO, 21 | originalContentUrl: videoUrl, 22 | previewImageUrl: previewImageUrl 23 | }; 24 | }; 25 | 26 | exports.buildAudio = function(audioUrl, durationMillis) { 27 | return { 28 | contentType: ContentType.AUDIO, 29 | originalContentUrl: audioUrl, 30 | contentMetadata: { AUDLEN: durationMillis.toString() } 31 | }; 32 | }; 33 | 34 | exports.buildLocation = function(text, latitude, longitude) { 35 | return { 36 | contentType: ContentType.LOCATION, 37 | text: text, 38 | location: { 39 | title: text, 40 | latitude: latitude, 41 | longitude: longitude 42 | } 43 | }; 44 | }; 45 | 46 | exports.buildSticker = function(stkid, stkpkgid, stkver) { 47 | return { 48 | contentType: ContentType.STICKER, 49 | contentMetadata: [stkver].reduce( function(meta, version) { 50 | if (version) meta.STKVER = version.toString(); 51 | return meta; 52 | }, { STKID: stkid.toString(), STKPKGID: stkpkgid.toString() }) 53 | }; 54 | }; 55 | 56 | exports.buildRichMessage = function(imageUrl, altText, markup) { 57 | return { 58 | contentType: ContentType.RICH_MESSAGE, 59 | contentMetadata: { 60 | SPEC_REV: '1', 61 | DOWNLOAD_URL: imageUrl, 62 | ALT_TEXT: altText, 63 | MARKUP_JSON: markup 64 | } 65 | }; 66 | }; 67 | -------------------------------------------------------------------------------- /lib/message/multipleMessagesBuilder.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../constants/ContentType'); 2 | var MultipleMessages = require('./MultipleMessages.js'); 3 | 4 | exports.buildMultipleMessages = function(multipleMessages, messageNotified) { 5 | if (!(multipleMessages instanceof MultipleMessages)) throw new Error('Invalid multipleMessages'); 6 | 7 | return { 8 | messages: multipleMessages.getMessages(), 9 | messageNotified: messageNotified 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /lib/receive/Message.js: -------------------------------------------------------------------------------- 1 | var Receive = require('./Receive'); 2 | 3 | function Message(config, result) { 4 | this.config = config; 5 | this.result = result; 6 | } 7 | 8 | Message.prototype = new Receive(); 9 | Message.prototype.constructor = Message; 10 | 11 | Message.prototype.getResult = function() { 12 | return this.result; 13 | }; 14 | 15 | Message.prototype.getConfig = function() { 16 | return this.config; 17 | }; 18 | 19 | Message.prototype.isMessage = function() { 20 | return true; 21 | }; 22 | 23 | Message.prototype.isSentMe = function() { 24 | return this.getResult().content.to.reduce((function(value, mid) { 25 | return value || this.getConfig().channelMID === mid; 26 | }).bind(this), false); 27 | }; 28 | 29 | Message.prototype.getContentId = function() { 30 | return this.getResult().content.id; 31 | }; 32 | 33 | Message.prototype.getCreatedTime = function() { 34 | return this.getResult().content.createdTime; 35 | }; 36 | 37 | Message.prototype.getFromMid = function() { 38 | return this.getResult().content.from || this.getResult().content.to[0]; 39 | }; 40 | 41 | Message.prototype.getMidType = function() { 42 | return this.getResult().content.from ? 'user' : 'group'; 43 | }; 44 | 45 | Message.prototype.isText = function() { 46 | return false; 47 | }; 48 | 49 | Message.prototype.isImage = function() { 50 | return false; 51 | }; 52 | 53 | Message.prototype.isVideo = function() { 54 | return false; 55 | }; 56 | 57 | Message.prototype.isAudio = function() { 58 | return false; 59 | }; 60 | 61 | Message.prototype.isLocation = function() { 62 | return false; 63 | }; 64 | 65 | Message.prototype.isSticker = function() { 66 | return false; 67 | }; 68 | 69 | Message.prototype.isContact = function() { 70 | return false; 71 | }; 72 | 73 | module.exports = Message; 74 | -------------------------------------------------------------------------------- /lib/receive/Operation.js: -------------------------------------------------------------------------------- 1 | var Receive = require('./Receive'); 2 | 3 | function Operation(config, result) { 4 | this.config = config; 5 | this.result = result; 6 | } 7 | 8 | Operation.prototype = new Receive(); 9 | Operation.prototype.constructor = Operation; 10 | 11 | Operation.prototype.isOperation = function() { 12 | return true; 13 | }; 14 | 15 | Operation.prototype.getResult = function() { 16 | return this.result; 17 | }; 18 | 19 | Operation.prototype.getConfig = function() { 20 | return this.config; 21 | }; 22 | 23 | Operation.prototype.getRevision = function() { 24 | return this.getResult().content.revision; 25 | }; 26 | 27 | Operation.prototype.getFromMid = function() { 28 | return this.getResult().content.params[0]; 29 | }; 30 | 31 | Operation.prototype.isAddContact = function() { 32 | return false; 33 | }; 34 | 35 | Operation.prototype.isBlockContact = function() { 36 | return false; 37 | }; 38 | 39 | Operation.prototype.isInvitedToGroup = function() { 40 | return false; 41 | }; 42 | 43 | Operation.prototype.isAddedToRoom = function() { 44 | return false; 45 | }; 46 | 47 | module.exports = Operation; 48 | -------------------------------------------------------------------------------- /lib/receive/Receive.js: -------------------------------------------------------------------------------- 1 | var BotAPIChannel = require('../constants/BotAPIChannel'); 2 | var method = Receive.prototype; 3 | function Receive(config, result) { 4 | this.config = config; 5 | this.result = result; 6 | } 7 | 8 | method.isMessage = function() { 9 | return false; 10 | }; 11 | 12 | method.isOperation = function() { 13 | return false; 14 | }; 15 | 16 | method.getResult = function() { 17 | return this.result; 18 | }; 19 | 20 | method.getConfig = function() { 21 | return this.config; 22 | }; 23 | 24 | method.isValidEvent = function() { 25 | var result = this.getResult(); 26 | var config = this.getConfig(); 27 | 28 | return result.toChannel === config.channelID && 29 | result.fromChannel === BotAPIChannel.RECEIVING_CHANNEL_ID && 30 | result.from === BotAPIChannel.RECEIVING_CHANNEL_MID; 31 | }; 32 | 33 | method.getId = function() { 34 | return this.getResult().id; 35 | }; 36 | 37 | method.validateSignature = function(rawJSON, signature) { 38 | return SignatureValidator.validateSignature(rawJSON, this.getConfig().channelSecret, signature); 39 | }; 40 | 41 | module.exports = Receive; 42 | -------------------------------------------------------------------------------- /lib/receive/ReceiveFactory.js: -------------------------------------------------------------------------------- 1 | var EventType = require('../constants/EventType'); 2 | var MessageReceiveFactory = require('./message/MessageReceiveFactory'); 3 | var OperationReceiveFactory = require('./operation/OperationReceiveFactory'); 4 | var UnsupportedEventTypeException = require('../exceptions/UnsupportedEventTypeException'); 5 | 6 | exports.create = function(config, result) { 7 | var eventType = result.eventType; 8 | switch (eventType) { 9 | case EventType.RECEIVING_MESSAGE: 10 | return MessageReceiveFactory.create(config, result); 11 | case EventType.RECEIVING_OPERATION: 12 | return OperationReceiveFactory.create(config, result); 13 | default: 14 | throw new UnsupportedEventTypeException('Undefined eventType: ' + eventType); 15 | } 16 | }; 17 | 18 | exports.createFromJSON = function(config, json) { 19 | if (!json) return []; 20 | return json.result.map(function(result) { 21 | return this.create(config, result); 22 | }.bind(this)); 23 | }; -------------------------------------------------------------------------------- /lib/receive/message/Audio.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../../constants/ContentType'); 2 | var Message = require('../Message'); 3 | 4 | var Audio = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | }; 8 | Audio.prototype = new Message(); 9 | Audio.prototype.constructor = Audio; 10 | 11 | Audio.prototype.isAudio = function() { 12 | return true; 13 | }; 14 | 15 | Audio.prototype.getContentType = function() { 16 | return ContentType.AUDIO; 17 | }; 18 | 19 | module.exports = Audio; 20 | -------------------------------------------------------------------------------- /lib/receive/message/Contact.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../../constants/ContentType'); 2 | var Message = require('../Message'); 3 | 4 | var Contact = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | this.meta = result.content.contentMetadata; 8 | }; 9 | Contact.prototype = new Message(); 10 | Contact.prototype.constructor = Contact; 11 | 12 | Contact.prototype.isContact = function() { 13 | return true; 14 | }; 15 | 16 | Contact.prototype.getContentType = function() { 17 | return ContentType.CONTACT; 18 | }; 19 | 20 | Contact.prototype.getMid = function() { 21 | return this.meta.mid; 22 | }; 23 | 24 | Contact.prototype.getDisplayName = function() { 25 | return this.meta.displayName; 26 | }; 27 | 28 | module.exports = Contact; 29 | -------------------------------------------------------------------------------- /lib/receive/message/Image.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../../constants/ContentType'); 2 | var Message = require('../Message'); 3 | 4 | var Image = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | }; 8 | Image.prototype = new Message(); 9 | Image.prototype.constructor = Image; 10 | 11 | Image.prototype.isImage = function() { 12 | return true; 13 | }; 14 | 15 | Image.prototype.getContentType = function() { 16 | return ContentType.IMAGE; 17 | }; 18 | 19 | Image.prototype.getImageUrl = function() { 20 | return this.result.originalContentUrl; 21 | }; 22 | 23 | module.exports = Image; 24 | -------------------------------------------------------------------------------- /lib/receive/message/Location.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../../constants/ContentType'); 2 | var Message = require('../Message'); 3 | 4 | var Location = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | this.location = result.content.location; 8 | }; 9 | Location.prototype = new Message(); 10 | Location.prototype.constructor = Location; 11 | 12 | Location.prototype.isLocation = function() { 13 | return true; 14 | }; 15 | 16 | Location.prototype.getContentType = function() { 17 | return ContentType.LOCATION; 18 | }; 19 | 20 | Location.prototype.getText = function() { 21 | return this.location.title; // `this.location.text` is always null 22 | }; 23 | 24 | Location.prototype.getTitle = function() { 25 | return this.location.title; 26 | }; 27 | 28 | Location.prototype.getAddress = function() { 29 | return this.location.address; 30 | }; 31 | 32 | Location.prototype.getLatitude = function() { 33 | return this.location.latitude; 34 | }; 35 | 36 | Location.prototype.getLongitude = function() { 37 | return this.location.longitude; 38 | }; 39 | 40 | module.exports = Location; 41 | -------------------------------------------------------------------------------- /lib/receive/message/MessageReceiveFactory.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../../constants/ContentType'); 2 | var UnsupportedContentTypeException = require('../../exceptions/UnsupportedContentTypeException'); 3 | 4 | var Text = require('./Text'); 5 | var Image = require('./Image'); 6 | var Audio = require('./Audio'); 7 | var Location = require('./Location'); 8 | var Sticker = require('./Sticker'); 9 | var Contact = require('./Contact'); 10 | 11 | exports.create = function(config, result) { 12 | var contentType = result.content.contentType; 13 | 14 | switch(contentType) { 15 | case ContentType.TEXT: 16 | return new Text(config, result); 17 | case ContentType.IMAGE: 18 | return new Image(config, result); 19 | case ContentType.VIDEO: 20 | return new Video(config, result); 21 | case ContentType.AUDIO: 22 | return new Audio(config, result); 23 | case ContentType.LOCATION: 24 | return new Location(config, result); 25 | case ContentType.STICKER: 26 | return new Sticker(config, result); 27 | case ContentType.CONTACT: 28 | return new Contact(config, result); 29 | default: 30 | throw new UnsupportedContentTypeException('Unsupported contentType is given: ' + contentType); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /lib/receive/message/Sticker.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../../constants/ContentType'); 2 | var Message = require('../Message'); 3 | 4 | var Sticker = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | this.meta = result.content.contentMetadata; 8 | }; 9 | Sticker.prototype = new Message(); 10 | Sticker.prototype.constructor = Sticker; 11 | 12 | Sticker.prototype.isSticker = function() { 13 | return true; 14 | }; 15 | 16 | Sticker.prototype.getContentType = function() { 17 | return ContentType.STICKER; 18 | }; 19 | 20 | Sticker.prototype.getStkPkgId = function() { 21 | return this.meta.STKPKGID; 22 | }; 23 | 24 | Sticker.prototype.getStkId = function() { 25 | return this.meta.STKID; 26 | }; 27 | 28 | Sticker.prototype.getStkVer = function() { 29 | return this.meta.STKVER; 30 | }; 31 | 32 | Sticker.prototype.getStkTxt = function() { 33 | return this.meta.STKTXT; 34 | }; 35 | 36 | module.exports = Sticker; 37 | -------------------------------------------------------------------------------- /lib/receive/message/Text.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../../constants/ContentType'); 2 | var Message = require('../Message'); 3 | 4 | var Text = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | }; 8 | Text.prototype = new Message(); 9 | Text.prototype.constructor = Text; 10 | 11 | Text.prototype.isText = function() { 12 | return true; 13 | }; 14 | 15 | Text.prototype.getContentType = function() { 16 | return ContentType.TEXT; 17 | }; 18 | 19 | Text.prototype.getText = function() { 20 | return this.result.content.text; 21 | }; 22 | 23 | module.exports = Text; 24 | -------------------------------------------------------------------------------- /lib/receive/message/Video.js: -------------------------------------------------------------------------------- 1 | var ContentType = require('../../constants/ContentType'); 2 | var Message = require('../Message'); 3 | 4 | var Video = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | }; 8 | Video.prototype = new Message(); 9 | Video.prototype.constructor = Video; 10 | 11 | Video.prototype.isVideo = function() { 12 | return true; 13 | }; 14 | 15 | Video.prototype.getContentType = function() { 16 | return ContentType.VIDEO; 17 | }; 18 | 19 | module.exports = Video; 20 | -------------------------------------------------------------------------------- /lib/receive/operation/AddContact.js: -------------------------------------------------------------------------------- 1 | var OpType = require('../../constants/OpType'); 2 | var Operation = require('../Operation'); 3 | 4 | var AddContact = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | }; 8 | AddContact.prototype = new Operation(); 9 | AddContact.prototype.constructor = AddContact; 10 | 11 | AddContact.prototype.isAddContact = function() { 12 | return true; 13 | }; 14 | 15 | AddContact.prototype.getOperationType = function() { 16 | return OpType.ADDED_AS_FRIEND; 17 | }; 18 | 19 | module.exports = AddContact; 20 | -------------------------------------------------------------------------------- /lib/receive/operation/AddedToRoom.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runnables/line-bot-sdk-nodejs/c30493c4a277d2bc888a145e9d2f906ca4504acb/lib/receive/operation/AddedToRoom.js -------------------------------------------------------------------------------- /lib/receive/operation/BlockContact.js: -------------------------------------------------------------------------------- 1 | var OpType = require('../../constants/OpType'); 2 | var Operation = require('../Operation'); 3 | 4 | var BlockContact = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | }; 8 | BlockContact.prototype = new Operation(); 9 | BlockContact.prototype.constructor = BlockContact; 10 | 11 | BlockContact.prototype.isBlockContact = function() { 12 | return true; 13 | }; 14 | 15 | BlockContact.prototype.getOperationType = function() { 16 | return OpType.BLOCKED; 17 | }; 18 | 19 | module.exports = BlockContact; 20 | -------------------------------------------------------------------------------- /lib/receive/operation/InvitedToGroup.js: -------------------------------------------------------------------------------- 1 | var OpType = require('../../constants/OpType'); 2 | var Operation = require('../Operation'); 3 | 4 | var InvitedToGroup = function(config, result) { 5 | this.config = config; 6 | this.result = result; 7 | }; 8 | InvitedToGroup.prototype = new Operation(); 9 | InvitedToGroup.prototype.constructor = InvitedToGroup; 10 | 11 | InvitedToGroup.prototype.isInvitedToGroup = function() { 12 | return true; 13 | }; 14 | 15 | InvitedToGroup.prototype.getOperationType = function() { 16 | return OpType.INVITED_TO_GROUP; 17 | }; 18 | 19 | module.exports = InvitedToGroup; 20 | -------------------------------------------------------------------------------- /lib/receive/operation/OperationReceiveFactory.js: -------------------------------------------------------------------------------- 1 | var OpType = require('../../constants/OpType'); 2 | var UnsupportedContentTypeException = require('../../exceptions/UnsupportedContentTypeException'); 3 | var AddContact = require('./AddContact'); 4 | var BlockContact = require('./BlockContact'); 5 | var InvitedToGroup = require('./InvitedToGroup'); 6 | var AddedToRoom = require('./AddedToRoom'); 7 | 8 | exports.create = function(config, result) { 9 | var opType = result.content.opType; 10 | switch (opType) { 11 | case OpType.ADDED_AS_FRIEND: 12 | return new AddContact(config, result); 13 | case OpType.BLOCKED: 14 | return new BlockContact(config, result); 15 | case OpType.INVITED_TO_GROUP: 16 | return new InvitedToGroup(config, result); 17 | case OpType.ADDED_TO_ROOM: 18 | return new AddedToRoom(config, result); 19 | default: 20 | throw new UnsupportedContentTypeException('Unsupported opType is given: ' + opType); 21 | } 22 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-bot-sdk", 3 | "version": "0.1.4", 4 | "description": "SDK of the LINE BOT API for Node.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/runnables/line-bot-sdk-nodejs.git" 12 | }, 13 | "keywords": [ 14 | "LINE", 15 | "Bot", 16 | "API", 17 | "Node.js" 18 | ], 19 | "author": "Runnables Company Limited", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/runnables/line-bot-sdk-nodejs/issues" 23 | }, 24 | "homepage": "https://github.com/runnables/line-bot-sdk-nodejs#readme", 25 | "dependencies": { 26 | "debug": "~2.2.0", 27 | "lodash.merge": "^4.3.5", 28 | "superagent": "^1.8.3", 29 | "superagent-promise": "^1.1.0" 30 | }, 31 | "devDependencies": { 32 | "chai": "~3.5.0", 33 | "chai-as-promised": "^5.3.0", 34 | "lodash": "^4.12.0", 35 | "mocha": "~2.4.5", 36 | "nock": "^8.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/testReceiveFactory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var chai = require('chai'); 3 | var should = chai.should(); 4 | 5 | var ReceiveFactory = require('../lib/receive/ReceiveFactory'); 6 | var Text = require('../lib/receive/message/Text'); 7 | var AddContact = require('../lib/receive/operation/AddContact'); 8 | 9 | describe('ReceiveFactory', function() { 10 | it('should be able to create receives from json', function(done) { 11 | var config = { 12 | channelID: '1441301333', 13 | channelSecret: 'testsecret', 14 | channelMID: 'u0a556cffd4da0dd89c94fb36e36e1cdc' 15 | }; 16 | 17 | var json = { 18 | result: [ 19 | { 20 | from: 'u206d25c2ea6bd87c17655609a1c37cb8', 21 | fromChannel: '1341301815', 22 | to: ['u0cc15697597f61dd8b01cea8b027050e'], 23 | toChannel: '1441301333', 24 | eventType: '138311609000106303', 25 | id: 'ABCDEF-12345678901', 26 | content: { 27 | id: '325708', 28 | createdTime: 1332394961610, 29 | from: 'uff2aec188e58752ee1fb0f9507c6529a', 30 | to: ['u0a556cffd4da0dd89c94fb36e36e1cdc'], 31 | toType: 1, 32 | contentType: 1, 33 | text: 'hello' 34 | } 35 | }, 36 | { 37 | from: 'u206d25c2ea6bd87c17655609a1c37cb8', 38 | fromChannel: '1341301815', 39 | to: ['u0cc15697597f61dd8b01cea8b027050e'], 40 | toChannel: '1441301333', 41 | eventType: '138311609100106403', 42 | id: 'ABCDEF-12345678902', 43 | content: { 44 | revision: 2469, 45 | opType: 4, 46 | params: [ 47 | 'u0f3bfc598b061eba02183bfc5280886a', 48 | null, 49 | null 50 | ] 51 | } 52 | } 53 | ] 54 | }; 55 | 56 | var receives = ReceiveFactory.createFromJSON(config, json); 57 | 58 | receives.should.be.an.instanceof(Array).that.has.lengthOf(2); 59 | 60 | receives[0].should.be.an.instanceof(Text); 61 | receives[0].isMessage().should.be.true; 62 | receives[0].isOperation().should.be.false; 63 | receives[0].isValidEvent().should.be.true; 64 | receives[0].isSentMe().should.be.true; 65 | receives[0].getId().should.equal('ABCDEF-12345678901'); 66 | receives[0].getContentId().should.equal('325708'); 67 | receives[0].getCreatedTime().should.equal(1332394961610); 68 | receives[0].getFromMid().should.equal('uff2aec188e58752ee1fb0f9507c6529a'); 69 | receives[0].isText().should.be.true; 70 | receives[0].getText().should.equal('hello'); 71 | 72 | receives[1].should.be.an.instanceof(AddContact); 73 | receives[1].isMessage().should.be.false; 74 | receives[1].isOperation().should.be.true; 75 | receives[1].isValidEvent().should.be.true; 76 | receives[1].isAddContact().should.be.true; 77 | receives[1].getRevision().should.equal(2469); 78 | receives[1].getFromMid().should.equal('u0f3bfc598b061eba02183bfc5280886a'); 79 | 80 | done(); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/testSendMessage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var chai = require('chai'); 3 | var chaiAsPromised = require('chai-as-promised'); 4 | var should = chai.should(); 5 | var nock = require('nock'); 6 | 7 | chai.use(chaiAsPromised); 8 | 9 | var LineBot = require('../'); 10 | var Markup = LineBot.Markup; 11 | var MultipleMessages = LineBot.MultipleMessages; 12 | var ContentType = require('../lib/constants/ContentType'); 13 | var RecipientType = require('../lib/constants/RecipientType'); 14 | 15 | function mockApi() { 16 | nock('https://trialbot-api.line.me') 17 | .post('/v1/events') 18 | .reply(200, { 19 | failed: [], 20 | messageId: '1460826285060', 21 | timestamp: 1460826285060, 22 | version: 1 23 | }); 24 | } 25 | 26 | describe('client#SendMessage', function() { 27 | var config = { 28 | channelID: '1000000000', 29 | channelSecret: 'testsecret', 30 | channelMID: 'TEST_MID' 31 | }; 32 | var client = LineBot.client(config); 33 | 34 | beforeEach(function() { 35 | mockApi(); 36 | }); 37 | 38 | it('should be able to send text', function() { 39 | var request = client.sendText(['DUMMY_MID'], 'hello!'); 40 | 41 | return Promise.all([ 42 | request.should.eventually.have.deep.property('statusCode', 200), 43 | request.should.eventually.have.deep.property('body.failed').that.be.empty, 44 | request.should.eventually.have.deep.property('body.messageId', '1460826285060'), 45 | request.should.eventually.have.deep.property('body.timestamp', 1460826285060), 46 | request.should.eventually.have.deep.property('body.version', 1), 47 | request.should.eventually.have.deep.property('request.method', 'POST'), 48 | request.should.eventually.have.deep.property('request.url', 'https://trialbot-api.line.me/v1/events'), 49 | request.should.eventually.have.deep.property('request._data.eventType', '138311608800106203'), 50 | request.should.eventually.have.deep.property('request._data.to').that.be.an('array').and.have.members(['DUMMY_MID']), 51 | request.should.eventually.have.deep.property('request._data.content.text', 'hello!'), 52 | request.should.eventually.have.deep.property('request._data.content.contentType', ContentType.TEXT), 53 | request.should.eventually.have.deep.property('request._data.content.toType', RecipientType.USER), 54 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelID', '1000000000'), 55 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelSecret', 'testsecret'), 56 | request.should.eventually.have.deep.property('request.header.X-Line-Trusted-User-With-ACL', 'TEST_MID') 57 | ]); 58 | }); 59 | 60 | it('should be able to send image', function() { 61 | var request = client.sendImage(['DUMMY_MID'], 'http://example.com/image.jpg', 'http://example.com/preview.jpg'); 62 | 63 | return Promise.all([ 64 | request.should.eventually.have.deep.property('statusCode', 200), 65 | request.should.eventually.have.deep.property('body.failed').that.be.empty, 66 | request.should.eventually.have.deep.property('body.messageId', '1460826285060'), 67 | request.should.eventually.have.deep.property('body.timestamp', 1460826285060), 68 | request.should.eventually.have.deep.property('body.version', 1), 69 | request.should.eventually.have.deep.property('request.method', 'POST'), 70 | request.should.eventually.have.deep.property('request.url', 'https://trialbot-api.line.me/v1/events'), 71 | request.should.eventually.have.deep.property('request._data.eventType', '138311608800106203'), 72 | request.should.eventually.have.deep.property('request._data.to').that.be.an('array').and.have.members(['DUMMY_MID']), 73 | request.should.eventually.have.deep.property('request._data.content.originalContentUrl', 'http://example.com/image.jpg'), 74 | request.should.eventually.have.deep.property('request._data.content.previewImageUrl', 'http://example.com/preview.jpg'), 75 | request.should.eventually.have.deep.property('request._data.content.contentType', ContentType.IMAGE), 76 | request.should.eventually.have.deep.property('request._data.content.toType', RecipientType.USER), 77 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelID', '1000000000'), 78 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelSecret', 'testsecret'), 79 | request.should.eventually.have.deep.property('request.header.X-Line-Trusted-User-With-ACL', 'TEST_MID') 80 | ]); 81 | }); 82 | 83 | it('should be able to send video', function() { 84 | var request = client.sendVideo(['DUMMY_MID'], 'http://example.com/video.mp4', 'http://example.com/preview.jpg'); 85 | 86 | return Promise.all([ 87 | request.should.eventually.have.deep.property('statusCode', 200), 88 | request.should.eventually.have.deep.property('body.failed').that.be.empty, 89 | request.should.eventually.have.deep.property('body.messageId', '1460826285060'), 90 | request.should.eventually.have.deep.property('body.timestamp', 1460826285060), 91 | request.should.eventually.have.deep.property('body.version', 1), 92 | request.should.eventually.have.deep.property('request.method', 'POST'), 93 | request.should.eventually.have.deep.property('request.url', 'https://trialbot-api.line.me/v1/events'), 94 | request.should.eventually.have.deep.property('request._data.eventType', '138311608800106203'), 95 | request.should.eventually.have.deep.property('request._data.to').that.be.an('array').and.have.members(['DUMMY_MID']), 96 | request.should.eventually.have.deep.property('request._data.content.originalContentUrl', 'http://example.com/video.mp4'), 97 | request.should.eventually.have.deep.property('request._data.content.previewImageUrl', 'http://example.com/preview.jpg'), 98 | request.should.eventually.have.deep.property('request._data.content.contentType', ContentType.VIDEO), 99 | request.should.eventually.have.deep.property('request._data.content.toType', RecipientType.USER), 100 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelID', '1000000000'), 101 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelSecret', 'testsecret'), 102 | request.should.eventually.have.deep.property('request.header.X-Line-Trusted-User-With-ACL', 'TEST_MID') 103 | ]); 104 | }); 105 | 106 | it('should be able to send audio', function() { 107 | var request = client.sendAudio(['DUMMY_MID'], 'http://example.com/sound.m4a', 5000); 108 | 109 | return Promise.all([ 110 | request.should.eventually.have.deep.property('statusCode', 200), 111 | request.should.eventually.have.deep.property('body.failed').that.be.empty, 112 | request.should.eventually.have.deep.property('body.messageId', '1460826285060'), 113 | request.should.eventually.have.deep.property('body.timestamp', 1460826285060), 114 | request.should.eventually.have.deep.property('body.version', 1), 115 | request.should.eventually.have.deep.property('request.method', 'POST'), 116 | request.should.eventually.have.deep.property('request.url', 'https://trialbot-api.line.me/v1/events'), 117 | request.should.eventually.have.deep.property('request._data.eventType', '138311608800106203'), 118 | request.should.eventually.have.deep.property('request._data.to').that.be.an('array').and.have.members(['DUMMY_MID']), 119 | request.should.eventually.have.deep.property('request._data.content.originalContentUrl', 'http://example.com/sound.m4a'), 120 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.AUDLEN', '5000'), 121 | request.should.eventually.have.deep.property('request._data.content.contentType', ContentType.AUDIO), 122 | request.should.eventually.have.deep.property('request._data.content.toType', RecipientType.USER), 123 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelID', '1000000000'), 124 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelSecret', 'testsecret'), 125 | request.should.eventually.have.deep.property('request.header.X-Line-Trusted-User-With-ACL', 'TEST_MID') 126 | ]); 127 | }); 128 | 129 | it('should be able to send location', function() { 130 | var request = client.sendLocation(['DUMMY_MID'], '2 Chome-21-1 Shibuya Tokyo 150-0002, Japan', 35.658240, 139.703478); 131 | 132 | return Promise.all([ 133 | request.should.eventually.have.deep.property('statusCode', 200), 134 | request.should.eventually.have.deep.property('body.failed').that.be.empty, 135 | request.should.eventually.have.deep.property('body.messageId', '1460826285060'), 136 | request.should.eventually.have.deep.property('body.timestamp', 1460826285060), 137 | request.should.eventually.have.deep.property('body.version', 1), 138 | request.should.eventually.have.deep.property('request.method', 'POST'), 139 | request.should.eventually.have.deep.property('request.url', 'https://trialbot-api.line.me/v1/events'), 140 | request.should.eventually.have.deep.property('request._data.eventType', '138311608800106203'), 141 | request.should.eventually.have.deep.property('request._data.to').that.be.an('array').and.have.members(['DUMMY_MID']), 142 | request.should.eventually.have.deep.property('request._data.content.text', '2 Chome-21-1 Shibuya Tokyo 150-0002, Japan'), 143 | request.should.eventually.have.deep.property('request._data.content.location.title', '2 Chome-21-1 Shibuya Tokyo 150-0002, Japan'), 144 | request.should.eventually.have.deep.property('request._data.content.location.latitude', 35.658240), 145 | request.should.eventually.have.deep.property('request._data.content.location.longitude', 139.703478), 146 | request.should.eventually.have.deep.property('request._data.content.contentType', ContentType.LOCATION), 147 | request.should.eventually.have.deep.property('request._data.content.toType', RecipientType.USER), 148 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelID', '1000000000'), 149 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelSecret', 'testsecret'), 150 | request.should.eventually.have.deep.property('request.header.X-Line-Trusted-User-With-ACL', 'TEST_MID') 151 | ]); 152 | }); 153 | 154 | it('should be able to send sticker', function() { 155 | var request = client.sendSticker(['DUMMY_MID'], 1, 2, 100); 156 | 157 | return Promise.all([ 158 | request.should.eventually.have.deep.property('statusCode', 200), 159 | request.should.eventually.have.deep.property('body.failed').that.be.empty, 160 | request.should.eventually.have.deep.property('body.messageId', '1460826285060'), 161 | request.should.eventually.have.deep.property('body.timestamp', 1460826285060), 162 | request.should.eventually.have.deep.property('body.version', 1), 163 | request.should.eventually.have.deep.property('request.method', 'POST'), 164 | request.should.eventually.have.deep.property('request.url', 'https://trialbot-api.line.me/v1/events'), 165 | request.should.eventually.have.deep.property('request._data.eventType', '138311608800106203'), 166 | request.should.eventually.have.deep.property('request._data.to').that.be.an('array').and.have.members(['DUMMY_MID']), 167 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.STKID', '1'), 168 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.STKPKGID', '2'), 169 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.STKVER', '100'), 170 | request.should.eventually.have.deep.property('request._data.content.contentType', ContentType.STICKER), 171 | request.should.eventually.have.deep.property('request._data.content.toType', RecipientType.USER), 172 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelID', '1000000000'), 173 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelSecret', 'testsecret'), 174 | request.should.eventually.have.deep.property('request.header.X-Line-Trusted-User-With-ACL', 'TEST_MID') 175 | ]); 176 | }); 177 | 178 | it('should be able to send rich message', function() { 179 | var markup = new Markup(1040); 180 | 181 | markup 182 | .setAction('SOMETHING', 'something', 'https://line.me') 183 | .addListener('SOMETHING', 0, 0, 520, 520); 184 | 185 | var expectedMarkup = { 186 | scenes: { 187 | scene1: { 188 | listeners: [{ 189 | params: [0, 0, 520, 520], 190 | type: 'touch', 191 | action: 'SOMETHING' 192 | }], 193 | draws: [{ 194 | image: 'image1', 195 | x: 0, 196 | y: 0, 197 | w: 1040, 198 | h: 1040 199 | }] 200 | } 201 | }, 202 | images: { 203 | image1: { 204 | x: 0, 205 | y: 0, 206 | w: 1040, 207 | h: 1040 208 | } 209 | }, 210 | actions: { 211 | 'SOMETHING': { 212 | text: 'something', 213 | params: { 214 | linkUri: 'https://line.me' 215 | }, 216 | type: 'web' 217 | } 218 | }, 219 | canvas: { 220 | initialScene: 'scene1', 221 | width: 1040, 222 | height: 1040 223 | } 224 | }; 225 | 226 | var request = client.sendRichMessage(['DUMMY_MID'], 'http://example.com/image.jpg', 'Alt text', markup); 227 | 228 | return Promise.all([ 229 | request.should.eventually.have.deep.property('statusCode', 200), 230 | request.should.eventually.have.deep.property('body.failed').that.be.empty, 231 | request.should.eventually.have.deep.property('body.messageId', '1460826285060'), 232 | request.should.eventually.have.deep.property('body.timestamp', 1460826285060), 233 | request.should.eventually.have.deep.property('body.version', 1), 234 | request.should.eventually.have.deep.property('request.method', 'POST'), 235 | request.should.eventually.have.deep.property('request.url', 'https://trialbot-api.line.me/v1/events'), 236 | request.should.eventually.have.deep.property('request._data.eventType', '138311608800106203'), 237 | request.should.eventually.have.deep.property('request._data.to').that.be.an('array').and.have.members(['DUMMY_MID']), 238 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.ALT_TEXT', 'Alt text'), 239 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.DOWNLOAD_URL', 'http://example.com/image.jpg'), 240 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.MARKUP_JSON.scenes.scene1.listeners').that.is.an('array') 241 | .with.members([expectedMarkup.scenes.scene1.listeners[0]]), 242 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.MARKUP_JSON.scenes.scene1.draws').that.is.an('array') 243 | .with.members([expectedMarkup.scenes.scene1.draws[0]]), 244 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.MARKUP_JSON.images').that.eql(expectedMarkup.images), 245 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.MARKUP_JSON.actions').that.eql(expectedMarkup.actions), 246 | request.should.eventually.have.deep.property('request._data.content.contentMetadata.MARKUP_JSON.canvas').that.eql(expectedMarkup.canvas), 247 | request.should.eventually.have.deep.property('request._data.content.contentType', ContentType.RICH_MESSAGE), 248 | request.should.eventually.have.deep.property('request._data.content.toType', RecipientType.USER), 249 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelID', '1000000000'), 250 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelSecret', 'testsecret'), 251 | request.should.eventually.have.deep.property('request.header.X-Line-Trusted-User-With-ACL', 'TEST_MID') 252 | ]); 253 | }); 254 | 255 | it('should be able to send multiple messages', function() { 256 | var multipleMessages = new MultipleMessages(); 257 | 258 | multipleMessages 259 | .addText('hello!') 260 | .addImage('http://example.com/image.jpg', 'http://example.com/preview.jpg') 261 | .addVideo('http://example.com/video.mp4', 'http://example.com/video_preview.jpg') 262 | .addAudio('http://example.com/audio.m4a', 5000) 263 | .addLocation('2 Chome-21-1 Shibuya Tokyo 150-0002, Japan', 35.658240, 139.703478) 264 | .addSticker(1, 2, 100); 265 | 266 | var request = client.sendMultipleMessages(['DUMMY_MID'], multipleMessages); 267 | 268 | return Promise.all([ 269 | request.should.eventually.have.deep.property('statusCode', 200), 270 | request.should.eventually.have.deep.property('body.failed').that.be.empty, 271 | request.should.eventually.have.deep.property('body.messageId', '1460826285060'), 272 | request.should.eventually.have.deep.property('body.timestamp', 1460826285060), 273 | request.should.eventually.have.deep.property('body.version', 1), 274 | request.should.eventually.have.deep.property('request.method', 'POST'), 275 | request.should.eventually.have.deep.property('request.url', 'https://trialbot-api.line.me/v1/events'), 276 | request.should.eventually.have.deep.property('request._data.eventType', '140177271400161403'), 277 | request.should.eventually.have.deep.property('request._data.to').that.be.an('array').and.have.members(['DUMMY_MID']), 278 | request.should.eventually.have.deep.property('request._data.content.messages').to.be.an('array').that.satisfy(function(messages) { 279 | var message0 = messages[0].text === 'hello!' && 280 | messages[0].contentType === ContentType.TEXT; 281 | var message1 = messages[1].originalContentUrl === 'http://example.com/image.jpg' && 282 | messages[1].previewImageUrl === 'http://example.com/preview.jpg' && 283 | messages[1].contentType === ContentType.IMAGE; 284 | var message2 = messages[2].originalContentUrl === 'http://example.com/video.mp4' && 285 | messages[2].previewImageUrl === 'http://example.com/video_preview.jpg' && 286 | messages[2].contentType === ContentType.VIDEO; 287 | var message3 = messages[3].originalContentUrl === 'http://example.com/audio.m4a' && 288 | messages[3].contentMetadata.AUDLEN === '5000' && 289 | messages[3].contentType === ContentType.AUDIO; 290 | var message4 = messages[4].text === '2 Chome-21-1 Shibuya Tokyo 150-0002, Japan' && 291 | messages[4].location.title === '2 Chome-21-1 Shibuya Tokyo 150-0002, Japan' && 292 | messages[4].location.latitude === 35.658240 && 293 | messages[4].location.longitude === 139.703478 && 294 | messages[4].contentType === ContentType.LOCATION; 295 | var message5 = messages[5].contentMetadata.STKID === '1' && 296 | messages[5].contentMetadata.STKPKGID === '2' && 297 | messages[5].contentMetadata.STKVER === '100' && 298 | messages[5].contentType === ContentType.STICKER; 299 | 300 | return message0 && message1 && message2 && message3 && message4 && message5; 301 | }), 302 | request.should.eventually.have.deep.property('request._data.content.toType', RecipientType.USER), 303 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelID', '1000000000'), 304 | request.should.eventually.have.deep.property('request.header.X-Line-ChannelSecret', 'testsecret'), 305 | request.should.eventually.have.deep.property('request.header.X-Line-Trusted-User-With-ACL', 'TEST_MID') 306 | ]); 307 | }); 308 | }); 309 | -------------------------------------------------------------------------------- /test/testSignatureValidator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var chai = require('chai'); 3 | var should = chai.should(); 4 | 5 | var SignatureValidator = require('../lib/SignatureValidator'); 6 | 7 | describe('SignatureValidator', function() { 8 | var config = { 9 | channelID: '1441301333', 10 | channelSecret: 'testsecret', 11 | channelMID: 'u0a556cffd4da0dd89c94fb36e36e1cdc' 12 | }; 13 | var rawJSON = '{\n\ 14 | "result":[\n\ 15 | {\n\ 16 | "from":"u206d25c2ea6bd87c17655609a1c37cb8",\n\ 17 | "fromChannel":"1341301815",\n\ 18 | "to":["u0cc15697597f61dd8b01cea8b027050e"],\n\ 19 | "toChannel":"1441301333",\n\ 20 | "eventType":"138311609000106303",\n\ 21 | "id":"ABCDEF-12345678901",\n\ 22 | "content":{\n\ 23 | "id":"325708",\n\ 24 | "createdTime":1332394961610,\n\ 25 | "from":"uff2aec188e58752ee1fb0f9507c6529a",\n\ 26 | "to":["u0a556cffd4da0dd89c94fb36e36e1cdc"],\n\ 27 | "toType":1,\n\ 28 | "contentType":1,\n\ 29 | "text":"hello"\n\ 30 | }\n\ 31 | },\n\ 32 | {\n\ 33 | "from":"u206d25c2ea6bd87c17655609a1c37cb8",\n\ 34 | "fromChannel":"1341301815",\n\ 35 | "to":["u0cc15697597f61dd8b01cea8b027050e"],\n\ 36 | "toChannel":"1441301333",\n\ 37 | "eventType":"138311609100106403",\n\ 38 | "id":"ABCDEF-12345678902",\n\ 39 | "content":{\n\ 40 | "revision":2469,\n\ 41 | "opType":4,\n\ 42 | "params":[\n\ 43 | "u0f3bfc598b061eba02183bfc5280886a",\n\ 44 | null,\n\ 45 | null\n\ 46 | ]\n\ 47 | }\n\ 48 | }\n\ 49 | ]\n\ 50 | }'; 51 | var signature = 'kPXp0nPWSzfWAapWHiesbcztpKnXJoX8krCa1CcTghk='; 52 | 53 | it('should be able to validate signature', function(done) { 54 | SignatureValidator.validateSignature(rawJSON, config.channelSecret, signature).should.be.true; 55 | SignatureValidator.validateSignature(rawJSON, config.channelSecret, 'XXX').should.be.false; 56 | done(); 57 | }); 58 | }); 59 | --------------------------------------------------------------------------------