김수민

commit

Showing 96 changed files with 24618 additions and 1 deletions
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
2 <project version="4"> 2 <project version="4">
3 <component name="ChangeListManager"> 3 <component name="ChangeListManager">
4 <list default="true" id="5c08ae96-2f87-46c1-b81d-f30e494ce252" name="Default Changelist" comment=""> 4 <list default="true" id="5c08ae96-2f87-46c1-b81d-f30e494ce252" name="Default Changelist" comment="">
5 - <change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
6 <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> 5 <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
7 <change beforePath="$PROJECT_DIR$/app.js" beforeDir="false" afterPath="$PROJECT_DIR$/app.js" afterDir="false" /> 6 <change beforePath="$PROJECT_DIR$/app.js" beforeDir="false" afterPath="$PROJECT_DIR$/app.js" afterDir="false" />
8 <change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" /> 7 <change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
...@@ -58,6 +57,7 @@ ...@@ -58,6 +57,7 @@
58 <workItem from="1573898557106" duration="89000" /> 57 <workItem from="1573898557106" duration="89000" />
59 <workItem from="1573899005304" duration="7410000" /> 58 <workItem from="1573899005304" duration="7410000" />
60 <workItem from="1574071758382" duration="6285000" /> 59 <workItem from="1574071758382" duration="6285000" />
60 + <workItem from="1574323490142" duration="7902000" />
61 </task> 61 </task>
62 <servers /> 62 <servers />
63 </component> 63 </component>
......
...@@ -105,10 +105,41 @@ function handleEvent(event) { ...@@ -105,10 +105,41 @@ function handleEvent(event) {
105 result.text = objBody.message.result.translatedText; 105 result.text = objBody.message.result.translatedText;
106 console.log(result.text); 106 console.log(result.text);
107 //번역된 문장 보내기 107 //번역된 문장 보내기
108 + let audio_options={
109 + 'Text': result.text,
110 + 'OutputFormat': 'mp3',
111 + 'VoiceId':'Seoyeon'
112 + };
113 + Polly.synthesizeSpeech(audio_options, (err, data) => {
114 + console.log("check");
115 + if (err) {
116 + throw err;
117 + } else if (data) {
118 + if (data.AudioStream instanceof Buffer) {
119 + fs.writeFile("./speech.mp3", data.AudioStream, function(err) {
120 + if (err) {
121 + return console.log(err);
122 + }
123 + console.log("The file was saved!");
124 + })
125 + }
126 + }
127 + });
128 + request.post(audio_options, function (error,response,body) {
129 + if(!error && response.statusCode == 200){
130 + for_audio_client.replyAudio(event.replyToken,{
131 + originalContentUrl: 'https://panguin.ml/speech.mp3',
132 + duration: 240000
133 + }).then(resolve).catch(reject);
134 + }
135 + });
108 client.replyMessage(event.replyToken,result).then(resolve).catch(reject); 136 client.replyMessage(event.replyToken,result).then(resolve).catch(reject);
109 137
110 } 138 }
111 }); 139 });
140 +
141 +
142 +
112 } 143 }
113 // 메시지의 언어가 영어 또는 한국어가 아닐 경우 144 // 메시지의 언어가 영어 또는 한국어가 아닐 경우
114 else{ 145 else{
......
1 +#!/bin/sh
2 +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3 +
4 +case `uname` in
5 + *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6 +esac
7 +
8 +if [ -x "$basedir/node" ]; then
9 + "$basedir/node" "$basedir/../loose-envify/cli.js" "$@"
10 + ret=$?
11 +else
12 + node "$basedir/../loose-envify/cli.js" "$@"
13 + ret=$?
14 +fi
15 +exit $ret
1 +@IF EXIST "%~dp0\node.exe" (
2 + "%~dp0\node.exe" "%~dp0\..\loose-envify\cli.js" %*
3 +) ELSE (
4 + @SETLOCAL
5 + @SET PATHEXT=%PATHEXT:;.JS;=;%
6 + node "%~dp0\..\loose-envify\cli.js" %*
7 +)
...\ No newline at end of file ...\ No newline at end of file
1 +The MIT License (MIT)
2 +
3 +Copyright (c) 2017-present Yoctol (github.com/Yoctol/messaging-apis)
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
13 +all 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
21 +THE SOFTWARE.
1 +# axios-error
2 +
3 +> An axios error wrapper that aim to provide clear error message to the user
4 +
5 +## Installation
6 +
7 +```sh
8 +npm i --save axios-error
9 +```
10 +
11 +or
12 +
13 +```sh
14 +yarn add axios-error
15 +```
16 +
17 +<br />
18 +
19 +## Usage
20 +
21 +```js
22 +const AxiosError = require('axios-error');
23 +
24 +// You can construct it from error throw by axios
25 +const error = new AxiosError(errorThrowByAxios);
26 +
27 +// Or with custom error message
28 +const error = new AxiosError(message, errorThrowByAxios);
29 +
30 +// Or construct it from axios config, axios request and axios response
31 +const error = new AxiosError(message, { config, request, response });
32 +```
33 +
34 +Directly `console.log` on the error instance will return formatted message. If you'd like to get the axios `request`, `response`, or `config`, you can still get them via those keys on the error instance.
35 +
36 +```js
37 +console.log(error); // formatted error message
38 +console.log(error.stack); // error stack trace
39 +console.log(error.config); // axios request config
40 +console.log(error.request); // HTTP request
41 +console.log(error.response); // HTTP response
42 +```
1 +"use strict";var _util = _interopRequireDefault(require("util"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}
2 +
3 +function indent(str) {
4 + return str.
5 + split('\n').
6 + map(s => s ? ` ${s}` : '').
7 + join('\n');
8 +}
9 +
10 +function json(data) {
11 + return JSON.stringify(data, null, 2);
12 +}
13 +
14 +module.exports = class AxiosError extends Error {
15 + constructor(messageOrErr, _err = {}) {
16 + let err;
17 + if (messageOrErr instanceof Error) {
18 + super(messageOrErr.message);
19 + err = messageOrErr;
20 + } else {
21 + super(messageOrErr);
22 + err = _err;
23 + }
24 + const { config, request, response } = err;
25 +
26 + this.config = config;
27 + this.request = request;
28 + this.response = response;
29 + if (response && response.status) {
30 + this.status = response.status;
31 + }
32 + }
33 +
34 + // TODO: remove inspect until we drop node < 6.6
35 + inspect(...args) {
36 + return this[_util.default.inspect.custom](...args);
37 + }
38 +
39 + [_util.default.inspect.custom]() {
40 + let requestMessage = '';
41 +
42 + if (this.config) {
43 + let { data } = this.config;
44 +
45 + try {
46 + data = JSON.parse(data);
47 + } catch (_) {} // eslint-disable-line
48 +
49 + let requestData = '';
50 +
51 + if (this.config.data) {
52 + requestData = `
53 +Request Data -
54 +${indent(json(data))}`;
55 + }
56 +
57 + requestMessage = `
58 +Request -
59 + ${this.config.method.toUpperCase()} ${this.config.url}
60 +${requestData}`;
61 + }
62 +
63 + let responseMessage = '';
64 +
65 + if (this.response) {
66 + let responseData;
67 +
68 + if (this.response.data) {
69 + responseData = `
70 +Response Data -
71 +${indent(json(this.response.data))}`;
72 + }
73 +
74 + responseMessage = `
75 +Response -
76 + ${this.response.status} ${this.response.statusText}
77 +${responseData}`;
78 + }
79 +
80 + return `
81 +${this.stack}
82 +
83 +Error Message -
84 + ${this.message}
85 +${requestMessage}
86 +${responseMessage}
87 +`;
88 + }};
...\ No newline at end of file ...\ No newline at end of file
1 +{
2 + "_from": "axios-error@^0.8.1",
3 + "_id": "axios-error@0.8.1",
4 + "_inBundle": false,
5 + "_integrity": "sha512-4YIf0FK2aO8HHVAQXvVFK2oPQSsOh3P1WQjkwQwO3oFjq4vO3S3YSyiRzyebBIR4NmcHaMJ6W5m0iHuisG7lTA==",
6 + "_location": "/axios-error",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "axios-error@^0.8.1",
12 + "name": "axios-error",
13 + "escapedName": "axios-error",
14 + "rawSpec": "^0.8.1",
15 + "saveSpec": null,
16 + "fetchSpec": "^0.8.1"
17 + },
18 + "_requiredBy": [
19 + "/messaging-api-line"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/axios-error/-/axios-error-0.8.1.tgz",
22 + "_shasum": "4d9c21370465c290d07192e7f3eacfff49c256ae",
23 + "_spec": "axios-error@^0.8.1",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\messaging-api-line",
25 + "bugs": {
26 + "url": "https://github.com/Yoctol/messaging-apis/issues"
27 + },
28 + "bundleDependencies": false,
29 + "deprecated": false,
30 + "description": "An axios error wrapper that aim to provide clear error message to the user",
31 + "devDependencies": {
32 + "axios": "^0.19.0",
33 + "axios-mock-adapter": "^1.17.0"
34 + },
35 + "engines": {
36 + "node": ">=8"
37 + },
38 + "gitHead": "622f5664634c26e752e4e54bd32dbc773271390f",
39 + "homepage": "https://github.com/Yoctol/messaging-apis#readme",
40 + "keywords": [
41 + "axios",
42 + "error",
43 + "http"
44 + ],
45 + "license": "MIT",
46 + "main": "lib/index.js",
47 + "name": "axios-error",
48 + "repository": {
49 + "type": "git",
50 + "url": "git+https://github.com/Yoctol/messaging-apis.git"
51 + },
52 + "version": "0.8.1"
53 +}
1 +// Jest Snapshot v1, https://goo.gl/fbAQLP
2 +
3 +exports[`should support error without axios data 1`] = `
4 +"
5 +Error: boom....
6 + at Object.<anonymous> (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:16:19)
7 + at Generator.throw (<anonymous>)
8 + at step (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:336)
9 + at /Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:535
10 + at <anonymous>
11 +
12 +
13 +Error Message -
14 + custom error
15 +
16 +
17 +"
18 +`;
19 +
20 +exports[`should work 1`] = `
21 +"
22 +Error: boom....
23 + at Object.<anonymous> (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:16:19)
24 + at Generator.throw (<anonymous>)
25 + at step (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:336)
26 + at /Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:535
27 + at <anonymous>
28 +
29 +
30 +Error Message -
31 + boom....
32 +
33 +Request -
34 + POST /
35 +
36 +Request Data -
37 + {
38 + \\"x\\": 1
39 + }
40 +
41 +Response -
42 + 400 Bad Request
43 +
44 +Response Data -
45 + {
46 + \\"error_status\\": \\"boom....\\"
47 + }
48 +"
49 +`;
50 +
51 +exports[`should work with construct using error instance only 1`] = `
52 +"
53 +Error: boom....
54 + at Object.<anonymous> (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:16:19)
55 + at Generator.throw (<anonymous>)
56 + at step (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:336)
57 + at /Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:535
58 + at <anonymous>
59 +
60 +
61 +Error Message -
62 + Request failed with status code 400
63 +
64 +Request -
65 + POST /
66 +
67 +Request Data -
68 + {
69 + \\"x\\": 1
70 + }
71 +
72 +Response -
73 + 400 Bad Request
74 +
75 +Response Data -
76 + {
77 + \\"error_status\\": \\"boom....\\"
78 + }
79 +"
80 +`;
81 +
82 +exports[`should work with undefined response 1`] = `
83 +"
84 +Error: boom....
85 + at Object.<anonymous> (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:16:19)
86 + at Generator.throw (<anonymous>)
87 + at step (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:336)
88 + at /Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:535
89 + at <anonymous>
90 +
91 +
92 +Error Message -
93 + read ECONNRESET
94 +
95 +Request -
96 + POST /
97 +
98 +Request Data -
99 + {
100 + \\"x\\": 1
101 + }
102 +
103 +"
104 +`;
1 +import util from 'util';
2 +
3 +import MockAdapter from 'axios-mock-adapter';
4 +import axios from 'axios';
5 +
6 +import AxiosError from '..';
7 +
8 +const mock = new MockAdapter(axios);
9 +
10 +mock.onAny().reply(400, {
11 + error_status: 'boom....',
12 +});
13 +
14 +const stack = `Error: boom....
15 + at Object.<anonymous> (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:16:19)
16 + at Generator.throw (<anonymous>)
17 + at step (/Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:336)
18 + at /Users/xxx/messaging-apis/packages/axios-error/src/__tests__/index.spec.js:4:535
19 + at <anonymous>
20 +`;
21 +
22 +it('should work', async () => {
23 + try {
24 + await axios.post('/', { x: 1 });
25 + } catch (err) {
26 + // overwrite because axios-mock-adapter set it to undefined
27 + err.response.statusText = 'Bad Request';
28 +
29 + const error = new AxiosError(err.response.data.error_status, err);
30 +
31 + // overwrite stack to test it
32 + error.stack = stack;
33 +
34 + expect(error[util.inspect.custom]()).toMatchSnapshot();
35 + }
36 +});
37 +
38 +it('should set `.status` property', async () => {
39 + try {
40 + await axios.post('/', { x: 1 });
41 + } catch (err) {
42 + // overwrite because axios-mock-adapter set it to undefined
43 + err.response.statusText = 'Bad Request';
44 +
45 + const error = new AxiosError(err);
46 +
47 + expect(error.status).toBe(400);
48 + }
49 +});
50 +
51 +it('should work with construct using error instance only', async () => {
52 + try {
53 + await axios.post('/', { x: 1 });
54 + } catch (err) {
55 + // overwrite because axios-mock-adapter set it to undefined
56 + err.response.statusText = 'Bad Request';
57 +
58 + const error = new AxiosError(err);
59 +
60 + // overwrite stack to test it
61 + error.stack = stack;
62 +
63 + expect(error[util.inspect.custom]()).toMatchSnapshot();
64 + }
65 +});
66 +
67 +it('should work with undefined response', async () => {
68 + try {
69 + await axios.post('/', { x: 1 });
70 + } catch (err) {
71 + // overwrite to undefined
72 + // https://github.com/Yoctol/bottender/issues/246
73 + err.response = undefined;
74 +
75 + const error = new AxiosError('read ECONNRESET', err);
76 +
77 + // overwrite stack to test it
78 + error.stack = stack;
79 +
80 + expect(error[util.inspect.custom]()).toMatchSnapshot();
81 + }
82 +});
83 +
84 +it('should support error without axios data', () => {
85 + const error = new AxiosError('custom error');
86 + error.stack = stack;
87 +
88 + expect(error[util.inspect.custom]()).toMatchSnapshot();
89 +});
1 +import util from 'util';
2 +
3 +function indent(str) {
4 + return str
5 + .split('\n')
6 + .map(s => (s ? ` ${s}` : ''))
7 + .join('\n');
8 +}
9 +
10 +function json(data) {
11 + return JSON.stringify(data, null, 2);
12 +}
13 +
14 +module.exports = class AxiosError extends Error {
15 + constructor(messageOrErr, _err = {}) {
16 + let err;
17 + if (messageOrErr instanceof Error) {
18 + super(messageOrErr.message);
19 + err = messageOrErr;
20 + } else {
21 + super(messageOrErr);
22 + err = _err;
23 + }
24 + const { config, request, response } = err;
25 +
26 + this.config = config;
27 + this.request = request;
28 + this.response = response;
29 + if (response && response.status) {
30 + this.status = response.status;
31 + }
32 + }
33 +
34 + // TODO: remove inspect until we drop node < 6.6
35 + inspect(...args) {
36 + return this[util.inspect.custom](...args);
37 + }
38 +
39 + [util.inspect.custom]() {
40 + let requestMessage = '';
41 +
42 + if (this.config) {
43 + let { data } = this.config;
44 +
45 + try {
46 + data = JSON.parse(data);
47 + } catch (_) {} // eslint-disable-line
48 +
49 + let requestData = '';
50 +
51 + if (this.config.data) {
52 + requestData = `
53 +Request Data -
54 +${indent(json(data))}`;
55 + }
56 +
57 + requestMessage = `
58 +Request -
59 + ${this.config.method.toUpperCase()} ${this.config.url}
60 +${requestData}`;
61 + }
62 +
63 + let responseMessage = '';
64 +
65 + if (this.response) {
66 + let responseData;
67 +
68 + if (this.response.data) {
69 + responseData = `
70 +Response Data -
71 +${indent(json(this.response.data))}`;
72 + }
73 +
74 + responseMessage = `
75 +Response -
76 + ${this.response.status} ${this.response.statusText}
77 +${responseData}`;
78 + }
79 +
80 + return `
81 +${this.stack}
82 +
83 +Error Message -
84 + ${this.message}
85 +${requestMessage}
86 +${responseMessage}
87 +`;
88 + }
89 +};
1 +/// <reference types="node"/>
2 +
3 +declare namespace imageType {
4 + type ImageType =
5 + | 'jpg'
6 + | 'png'
7 + | 'gif'
8 + | 'webp'
9 + | 'flif'
10 + | 'cr2'
11 + | 'tif'
12 + | 'bmp'
13 + | 'jxr'
14 + | 'psd'
15 + | 'ico'
16 + | 'bpg'
17 + | 'jp2'
18 + | 'jpm'
19 + | 'jpx'
20 + | 'heic'
21 + | 'cur'
22 + | 'dcm';
23 +
24 + interface ImageTypeResult {
25 + /**
26 + One of the supported [file types](https://github.com/sindresorhus/image-type#supported-file-types).
27 + */
28 + ext: ImageType;
29 +
30 + /**
31 + The detected [MIME type](https://en.wikipedia.org/wiki/Internet_media_type).
32 + */
33 + mime: string;
34 + }
35 +}
36 +
37 +declare const imageType: {
38 + /**
39 + Detect the image type of a `Buffer`/`Uint8Array`.
40 +
41 + @param input - Input to examine to determine the file type. It only needs the first `.minimumBytes` bytes.
42 +
43 + @example
44 + ```
45 + import readChunk = require('read-chunk');
46 + import imageType = require('image-type');
47 +
48 + const buffer = readChunk.sync('unicorn.png', 0, 12);
49 +
50 + imageType(buffer);
51 + //=> {ext: 'png', mime: 'image/png'}
52 +
53 +
54 + // Or from a remote location:
55 + import * as http from 'http';
56 +
57 + const url = 'https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif';
58 +
59 + http.get(url, response => {
60 + response.on('readable', () => {
61 + const chunk = response.read(imageType.minimumBytes);
62 + response.destroy();
63 + console.log(imageType(chunk));
64 + //=> {ext: 'gif', mime: 'image/gif'}
65 + });
66 + });
67 + ```
68 + */
69 + (input: Buffer | Uint8Array): imageType.ImageTypeResult | null;
70 +
71 + /**
72 + The minimum amount of bytes needed to detect a file type. Currently, it's 4100 bytes, but it can change, so don't hard-code it.
73 + */
74 + readonly minimumBytes: number;
75 +
76 + // TODO: Remove this for the next major release
77 + default: typeof imageType;
78 +};
79 +
80 +export = imageType;
1 +'use strict';
2 +const fileType = require('file-type');
3 +
4 +const imageExts = new Set([
5 + 'jpg',
6 + 'png',
7 + 'gif',
8 + 'webp',
9 + 'flif',
10 + 'cr2',
11 + 'tif',
12 + 'bmp',
13 + 'jxr',
14 + 'psd',
15 + 'ico',
16 + 'bpg',
17 + 'jp2',
18 + 'jpm',
19 + 'jpx',
20 + 'heic',
21 + 'cur',
22 + 'dcm'
23 +]);
24 +
25 +const imageType = input => {
26 + const ret = fileType(input);
27 + return imageExts.has(ret && ret.ext) ? ret : null;
28 +};
29 +
30 +module.exports = imageType;
31 +// TODO: Remove this for the next major release
32 +module.exports.default = imageType;
33 +
34 +Object.defineProperty(imageType, 'minimumBytes', {value: fileType.minimumBytes});
1 +MIT License
2 +
3 +Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
4 +
5 +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 +
7 +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 +
9 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1 +/// <reference types="node"/>
2 +import {Readable as ReadableStream} from 'stream';
3 +
4 +declare namespace fileType {
5 + type FileType =
6 + | 'jpg'
7 + | 'png'
8 + | 'gif'
9 + | 'webp'
10 + | 'flif'
11 + | 'cr2'
12 + | 'tif'
13 + | 'bmp'
14 + | 'jxr'
15 + | 'psd'
16 + | 'zip'
17 + | 'tar'
18 + | 'rar'
19 + | 'gz'
20 + | 'bz2'
21 + | '7z'
22 + | 'dmg'
23 + | 'mp4'
24 + | 'm4v'
25 + | 'mid'
26 + | 'mkv'
27 + | 'webm'
28 + | 'mov'
29 + | 'avi'
30 + | 'wmv'
31 + | 'mpg'
32 + | 'mp2'
33 + | 'mp3'
34 + | 'm4a'
35 + | 'ogg'
36 + | 'opus'
37 + | 'flac'
38 + | 'wav'
39 + | 'qcp'
40 + | 'amr'
41 + | 'pdf'
42 + | 'epub'
43 + | 'mobi'
44 + | 'exe'
45 + | 'swf'
46 + | 'rtf'
47 + | 'woff'
48 + | 'woff2'
49 + | 'eot'
50 + | 'ttf'
51 + | 'otf'
52 + | 'ico'
53 + | 'flv'
54 + | 'ps'
55 + | 'xz'
56 + | 'sqlite'
57 + | 'nes'
58 + | 'crx'
59 + | 'xpi'
60 + | 'cab'
61 + | 'deb'
62 + | 'ar'
63 + | 'rpm'
64 + | 'Z'
65 + | 'lz'
66 + | 'msi'
67 + | 'mxf'
68 + | 'mts'
69 + | 'wasm'
70 + | 'blend'
71 + | 'bpg'
72 + | 'docx'
73 + | 'pptx'
74 + | 'xlsx'
75 + | '3gp'
76 + | 'jp2'
77 + | 'jpm'
78 + | 'jpx'
79 + | 'mj2'
80 + | 'aif'
81 + | 'odt'
82 + | 'ods'
83 + | 'odp'
84 + | 'xml'
85 + | 'heic'
86 + | 'cur'
87 + | 'ktx'
88 + | 'ape'
89 + | 'wv'
90 + | 'asf'
91 + | 'wma'
92 + | 'wmv'
93 + | 'dcm'
94 + | 'mpc'
95 + | 'ics'
96 + | 'glb'
97 + | 'pcap';
98 +
99 + interface FileTypeResult {
100 + /**
101 + One of the supported [file types](https://github.com/sindresorhus/file-type#supported-file-types).
102 + */
103 + ext: FileType;
104 +
105 + /**
106 + The detected [MIME type](https://en.wikipedia.org/wiki/Internet_media_type).
107 + */
108 + mime: string;
109 + }
110 +
111 + type ReadableStreamWithFileType = ReadableStream & {
112 + readonly fileType: FileTypeResult | null;
113 + };
114 +}
115 +
116 +declare const fileType: {
117 + /**
118 + Detect the file type of a `Buffer`/`Uint8Array`/`ArrayBuffer`. The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
119 +
120 + @param buffer - It only needs the first `.minimumBytes` bytes. The exception is detection of `docx`, `pptx`, and `xlsx` which potentially requires reading the whole file.
121 + @returns An object with the detected file type and MIME type or `null` when there was no match.
122 +
123 + @example
124 + ```
125 + import readChunk = require('read-chunk');
126 + import fileType = require('file-type');
127 +
128 + const buffer = readChunk.sync('unicorn.png', 0, fileType.minimumBytes);
129 +
130 + fileType(buffer);
131 + //=> {ext: 'png', mime: 'image/png'}
132 +
133 +
134 + // Or from a remote location:
135 +
136 + import * as http from 'http';
137 +
138 + const url = 'https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif';
139 +
140 + http.get(url, response => {
141 + response.on('readable', () => {
142 + const chunk = response.read(fileType.minimumBytes);
143 + response.destroy();
144 + console.log(fileType(chunk));
145 + //=> {ext: 'gif', mime: 'image/gif'}
146 + });
147 + });
148 + ```
149 + */
150 + (buffer: Buffer | Uint8Array | ArrayBuffer): fileType.FileTypeResult | null;
151 +
152 + /**
153 + The minimum amount of bytes needed to detect a file type. Currently, it's 4100 bytes, but it can change, so don't hard-code it.
154 + */
155 + readonly minimumBytes: number;
156 +
157 + /**
158 + Detect the file type of a readable stream.
159 +
160 + @param readableStream - A readable stream containing a file to examine, see: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable).
161 + @returns A `Promise` which resolves to the original readable stream argument, but with an added `fileType` property, which is an object like the one returned from `fileType()`.
162 +
163 + @example
164 + ```
165 + import * as fs from 'fs';
166 + import * as crypto from 'crypto';
167 + import fileType = require('file-type');
168 +
169 + (async () => {
170 + const read = fs.createReadStream('encrypted.enc');
171 + const decipher = crypto.createDecipheriv(alg, key, iv);
172 +
173 + const stream = await fileType.stream(read.pipe(decipher));
174 +
175 + console.log(stream.fileType);
176 + //=> {ext: 'mov', mime: 'video/quicktime'}
177 +
178 + const write = fs.createWriteStream(`decrypted.${stream.fileType.ext}`);
179 + stream.pipe(write);
180 + })();
181 + ```
182 + */
183 + readonly stream: (
184 + readableStream: ReadableStream
185 + ) => Promise<fileType.ReadableStreamWithFileType>;
186 +
187 + // TODO: Remove this for the next major release
188 + readonly default: typeof fileType;
189 +};
190 +
191 +export = fileType;
1 +'use strict';
2 +const toBytes = s => [...s].map(c => c.charCodeAt(0));
3 +const xpiZipFilename = toBytes('META-INF/mozilla.rsa');
4 +const oxmlContentTypes = toBytes('[Content_Types].xml');
5 +const oxmlRels = toBytes('_rels/.rels');
6 +
7 +function readUInt64LE(buf, offset = 0) {
8 + let n = buf[offset];
9 + let mul = 1;
10 + let i = 0;
11 + while (++i < 8) {
12 + mul *= 0x100;
13 + n += buf[offset + i] * mul;
14 + }
15 +
16 + return n;
17 +}
18 +
19 +const fileType = input => {
20 + if (!(input instanceof Uint8Array || input instanceof ArrayBuffer || Buffer.isBuffer(input))) {
21 + throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`Buffer\` or \`ArrayBuffer\`, got \`${typeof input}\``);
22 + }
23 +
24 + const buf = input instanceof Uint8Array ? input : new Uint8Array(input);
25 +
26 + if (!(buf && buf.length > 1)) {
27 + return null;
28 + }
29 +
30 + const check = (header, options) => {
31 + options = Object.assign({
32 + offset: 0
33 + }, options);
34 +
35 + for (let i = 0; i < header.length; i++) {
36 + // If a bitmask is set
37 + if (options.mask) {
38 + // If header doesn't equal `buf` with bits masked off
39 + if (header[i] !== (options.mask[i] & buf[i + options.offset])) {
40 + return false;
41 + }
42 + } else if (header[i] !== buf[i + options.offset]) {
43 + return false;
44 + }
45 + }
46 +
47 + return true;
48 + };
49 +
50 + const checkString = (header, options) => check(toBytes(header), options);
51 +
52 + if (check([0xFF, 0xD8, 0xFF])) {
53 + return {
54 + ext: 'jpg',
55 + mime: 'image/jpeg'
56 + };
57 + }
58 +
59 + if (check([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) {
60 + return {
61 + ext: 'png',
62 + mime: 'image/png'
63 + };
64 + }
65 +
66 + if (check([0x47, 0x49, 0x46])) {
67 + return {
68 + ext: 'gif',
69 + mime: 'image/gif'
70 + };
71 + }
72 +
73 + if (check([0x57, 0x45, 0x42, 0x50], {offset: 8})) {
74 + return {
75 + ext: 'webp',
76 + mime: 'image/webp'
77 + };
78 + }
79 +
80 + if (check([0x46, 0x4C, 0x49, 0x46])) {
81 + return {
82 + ext: 'flif',
83 + mime: 'image/flif'
84 + };
85 + }
86 +
87 + // Needs to be before `tif` check
88 + if (
89 + (check([0x49, 0x49, 0x2A, 0x0]) || check([0x4D, 0x4D, 0x0, 0x2A])) &&
90 + check([0x43, 0x52], {offset: 8})
91 + ) {
92 + return {
93 + ext: 'cr2',
94 + mime: 'image/x-canon-cr2'
95 + };
96 + }
97 +
98 + if (
99 + check([0x49, 0x49, 0x2A, 0x0]) ||
100 + check([0x4D, 0x4D, 0x0, 0x2A])
101 + ) {
102 + return {
103 + ext: 'tif',
104 + mime: 'image/tiff'
105 + };
106 + }
107 +
108 + if (check([0x42, 0x4D])) {
109 + return {
110 + ext: 'bmp',
111 + mime: 'image/bmp'
112 + };
113 + }
114 +
115 + if (check([0x49, 0x49, 0xBC])) {
116 + return {
117 + ext: 'jxr',
118 + mime: 'image/vnd.ms-photo'
119 + };
120 + }
121 +
122 + if (check([0x38, 0x42, 0x50, 0x53])) {
123 + return {
124 + ext: 'psd',
125 + mime: 'image/vnd.adobe.photoshop'
126 + };
127 + }
128 +
129 + // Zip-based file formats
130 + // Need to be before the `zip` check
131 + if (check([0x50, 0x4B, 0x3, 0x4])) {
132 + if (
133 + check([0x6D, 0x69, 0x6D, 0x65, 0x74, 0x79, 0x70, 0x65, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2F, 0x65, 0x70, 0x75, 0x62, 0x2B, 0x7A, 0x69, 0x70], {offset: 30})
134 + ) {
135 + return {
136 + ext: 'epub',
137 + mime: 'application/epub+zip'
138 + };
139 + }
140 +
141 + // Assumes signed `.xpi` from addons.mozilla.org
142 + if (check(xpiZipFilename, {offset: 30})) {
143 + return {
144 + ext: 'xpi',
145 + mime: 'application/x-xpinstall'
146 + };
147 + }
148 +
149 + if (checkString('mimetypeapplication/vnd.oasis.opendocument.text', {offset: 30})) {
150 + return {
151 + ext: 'odt',
152 + mime: 'application/vnd.oasis.opendocument.text'
153 + };
154 + }
155 +
156 + if (checkString('mimetypeapplication/vnd.oasis.opendocument.spreadsheet', {offset: 30})) {
157 + return {
158 + ext: 'ods',
159 + mime: 'application/vnd.oasis.opendocument.spreadsheet'
160 + };
161 + }
162 +
163 + if (checkString('mimetypeapplication/vnd.oasis.opendocument.presentation', {offset: 30})) {
164 + return {
165 + ext: 'odp',
166 + mime: 'application/vnd.oasis.opendocument.presentation'
167 + };
168 + }
169 +
170 + // The docx, xlsx and pptx file types extend the Office Open XML file format:
171 + // https://en.wikipedia.org/wiki/Office_Open_XML_file_formats
172 + // We look for:
173 + // - one entry named '[Content_Types].xml' or '_rels/.rels',
174 + // - one entry indicating specific type of file.
175 + // MS Office, OpenOffice and LibreOffice may put the parts in different order, so the check should not rely on it.
176 + const findNextZipHeaderIndex = (arr, startAt = 0) => arr.findIndex((el, i, arr) => i >= startAt && arr[i] === 0x50 && arr[i + 1] === 0x4B && arr[i + 2] === 0x3 && arr[i + 3] === 0x4);
177 +
178 + let zipHeaderIndex = 0; // The first zip header was already found at index 0
179 + let oxmlFound = false;
180 + let type = null;
181 +
182 + do {
183 + const offset = zipHeaderIndex + 30;
184 +
185 + if (!oxmlFound) {
186 + oxmlFound = (check(oxmlContentTypes, {offset}) || check(oxmlRels, {offset}));
187 + }
188 +
189 + if (!type) {
190 + if (checkString('word/', {offset})) {
191 + type = {
192 + ext: 'docx',
193 + mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
194 + };
195 + } else if (checkString('ppt/', {offset})) {
196 + type = {
197 + ext: 'pptx',
198 + mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
199 + };
200 + } else if (checkString('xl/', {offset})) {
201 + type = {
202 + ext: 'xlsx',
203 + mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
204 + };
205 + }
206 + }
207 +
208 + if (oxmlFound && type) {
209 + return type;
210 + }
211 +
212 + zipHeaderIndex = findNextZipHeaderIndex(buf, offset);
213 + } while (zipHeaderIndex >= 0);
214 +
215 + // No more zip parts available in the buffer, but maybe we are almost certain about the type?
216 + if (type) {
217 + return type;
218 + }
219 + }
220 +
221 + if (
222 + check([0x50, 0x4B]) &&
223 + (buf[2] === 0x3 || buf[2] === 0x5 || buf[2] === 0x7) &&
224 + (buf[3] === 0x4 || buf[3] === 0x6 || buf[3] === 0x8)
225 + ) {
226 + return {
227 + ext: 'zip',
228 + mime: 'application/zip'
229 + };
230 + }
231 +
232 + if (check([0x75, 0x73, 0x74, 0x61, 0x72], {offset: 257})) {
233 + return {
234 + ext: 'tar',
235 + mime: 'application/x-tar'
236 + };
237 + }
238 +
239 + if (
240 + check([0x52, 0x61, 0x72, 0x21, 0x1A, 0x7]) &&
241 + (buf[6] === 0x0 || buf[6] === 0x1)
242 + ) {
243 + return {
244 + ext: 'rar',
245 + mime: 'application/x-rar-compressed'
246 + };
247 + }
248 +
249 + if (check([0x1F, 0x8B, 0x8])) {
250 + return {
251 + ext: 'gz',
252 + mime: 'application/gzip'
253 + };
254 + }
255 +
256 + if (check([0x42, 0x5A, 0x68])) {
257 + return {
258 + ext: 'bz2',
259 + mime: 'application/x-bzip2'
260 + };
261 + }
262 +
263 + if (check([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) {
264 + return {
265 + ext: '7z',
266 + mime: 'application/x-7z-compressed'
267 + };
268 + }
269 +
270 + if (check([0x78, 0x01])) {
271 + return {
272 + ext: 'dmg',
273 + mime: 'application/x-apple-diskimage'
274 + };
275 + }
276 +
277 + if (check([0x33, 0x67, 0x70, 0x35]) || // 3gp5
278 + (
279 + check([0x0, 0x0, 0x0]) && check([0x66, 0x74, 0x79, 0x70], {offset: 4}) &&
280 + (
281 + check([0x6D, 0x70, 0x34, 0x31], {offset: 8}) || // MP41
282 + check([0x6D, 0x70, 0x34, 0x32], {offset: 8}) || // MP42
283 + check([0x69, 0x73, 0x6F, 0x6D], {offset: 8}) || // ISOM
284 + check([0x69, 0x73, 0x6F, 0x32], {offset: 8}) || // ISO2
285 + check([0x6D, 0x6D, 0x70, 0x34], {offset: 8}) || // MMP4
286 + check([0x4D, 0x34, 0x56], {offset: 8}) || // M4V
287 + check([0x64, 0x61, 0x73, 0x68], {offset: 8}) // DASH
288 + )
289 + )) {
290 + return {
291 + ext: 'mp4',
292 + mime: 'video/mp4'
293 + };
294 + }
295 +
296 + if (check([0x4D, 0x54, 0x68, 0x64])) {
297 + return {
298 + ext: 'mid',
299 + mime: 'audio/midi'
300 + };
301 + }
302 +
303 + // https://github.com/threatstack/libmagic/blob/master/magic/Magdir/matroska
304 + if (check([0x1A, 0x45, 0xDF, 0xA3])) {
305 + const sliced = buf.subarray(4, 4 + 4096);
306 + const idPos = sliced.findIndex((el, i, arr) => arr[i] === 0x42 && arr[i + 1] === 0x82);
307 +
308 + if (idPos !== -1) {
309 + const docTypePos = idPos + 3;
310 + const findDocType = type => [...type].every((c, i) => sliced[docTypePos + i] === c.charCodeAt(0));
311 +
312 + if (findDocType('matroska')) {
313 + return {
314 + ext: 'mkv',
315 + mime: 'video/x-matroska'
316 + };
317 + }
318 +
319 + if (findDocType('webm')) {
320 + return {
321 + ext: 'webm',
322 + mime: 'video/webm'
323 + };
324 + }
325 + }
326 + }
327 +
328 + if (check([0x0, 0x0, 0x0, 0x14, 0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20]) ||
329 + check([0x66, 0x72, 0x65, 0x65], {offset: 4}) || // Type: `free`
330 + check([0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20], {offset: 4}) ||
331 + check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) || // MJPEG
332 + check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) || // Type: `moov`
333 + check([0x77, 0x69, 0x64, 0x65], {offset: 4})) {
334 + return {
335 + ext: 'mov',
336 + mime: 'video/quicktime'
337 + };
338 + }
339 +
340 + // RIFF file format which might be AVI, WAV, QCP, etc
341 + if (check([0x52, 0x49, 0x46, 0x46])) {
342 + if (check([0x41, 0x56, 0x49], {offset: 8})) {
343 + return {
344 + ext: 'avi',
345 + mime: 'video/vnd.avi'
346 + };
347 + }
348 +
349 + if (check([0x57, 0x41, 0x56, 0x45], {offset: 8})) {
350 + return {
351 + ext: 'wav',
352 + mime: 'audio/vnd.wave'
353 + };
354 + }
355 +
356 + // QLCM, QCP file
357 + if (check([0x51, 0x4C, 0x43, 0x4D], {offset: 8})) {
358 + return {
359 + ext: 'qcp',
360 + mime: 'audio/qcelp'
361 + };
362 + }
363 + }
364 +
365 + // ASF_Header_Object first 80 bytes
366 + if (check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) {
367 + // Search for header should be in first 1KB of file.
368 +
369 + let offset = 30;
370 + do {
371 + const objectSize = readUInt64LE(buf, offset + 16);
372 + if (check([0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65], {offset})) {
373 + // Sync on Stream-Properties-Object (B7DC0791-A9B7-11CF-8EE6-00C00C205365)
374 + if (check([0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B], {offset: offset + 24})) {
375 + // Found audio:
376 + return {
377 + ext: 'wma',
378 + mime: 'audio/x-ms-wma'
379 + };
380 + }
381 +
382 + if (check([0xC0, 0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B], {offset: offset + 24})) {
383 + // Found video:
384 + return {
385 + ext: 'wmv',
386 + mime: 'video/x-ms-asf'
387 + };
388 + }
389 +
390 + break;
391 + }
392 +
393 + offset += objectSize;
394 + } while (offset + 24 <= buf.length);
395 +
396 + // Default to ASF generic extension
397 + return {
398 + ext: 'asf',
399 + mime: 'application/vnd.ms-asf'
400 + };
401 + }
402 +
403 + if (
404 + check([0x0, 0x0, 0x1, 0xBA]) ||
405 + check([0x0, 0x0, 0x1, 0xB3])
406 + ) {
407 + return {
408 + ext: 'mpg',
409 + mime: 'video/mpeg'
410 + };
411 + }
412 +
413 + if (check([0x66, 0x74, 0x79, 0x70, 0x33, 0x67], {offset: 4})) {
414 + return {
415 + ext: '3gp',
416 + mime: 'video/3gpp'
417 + };
418 + }
419 +
420 + // Check for MPEG header at different starting offsets
421 + for (let start = 0; start < 2 && start < (buf.length - 16); start++) {
422 + if (
423 + check([0x49, 0x44, 0x33], {offset: start}) || // ID3 header
424 + check([0xFF, 0xE2], {offset: start, mask: [0xFF, 0xE2]}) // MPEG 1 or 2 Layer 3 header
425 + ) {
426 + return {
427 + ext: 'mp3',
428 + mime: 'audio/mpeg'
429 + };
430 + }
431 +
432 + if (
433 + check([0xFF, 0xE4], {offset: start, mask: [0xFF, 0xE4]}) // MPEG 1 or 2 Layer 2 header
434 + ) {
435 + return {
436 + ext: 'mp2',
437 + mime: 'audio/mpeg'
438 + };
439 + }
440 +
441 + if (
442 + check([0xFF, 0xF8], {offset: start, mask: [0xFF, 0xFC]}) // MPEG 2 layer 0 using ADTS
443 + ) {
444 + return {
445 + ext: 'mp2',
446 + mime: 'audio/mpeg'
447 + };
448 + }
449 +
450 + if (
451 + check([0xFF, 0xF0], {offset: start, mask: [0xFF, 0xFC]}) // MPEG 4 layer 0 using ADTS
452 + ) {
453 + return {
454 + ext: 'mp4',
455 + mime: 'audio/mpeg'
456 + };
457 + }
458 + }
459 +
460 + if (
461 + check([0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41], {offset: 4})
462 + ) {
463 + return { // MPEG-4 layer 3 (audio)
464 + ext: 'm4a',
465 + mime: 'audio/mp4' // RFC 4337
466 + };
467 + }
468 +
469 + // Needs to be before `ogg` check
470 + if (check([0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64], {offset: 28})) {
471 + return {
472 + ext: 'opus',
473 + mime: 'audio/opus'
474 + };
475 + }
476 +
477 + // If 'OggS' in first bytes, then OGG container
478 + if (check([0x4F, 0x67, 0x67, 0x53])) {
479 + // This is a OGG container
480 +
481 + // If ' theora' in header.
482 + if (check([0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61], {offset: 28})) {
483 + return {
484 + ext: 'ogv',
485 + mime: 'video/ogg'
486 + };
487 + }
488 +
489 + // If '\x01video' in header.
490 + if (check([0x01, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x00], {offset: 28})) {
491 + return {
492 + ext: 'ogm',
493 + mime: 'video/ogg'
494 + };
495 + }
496 +
497 + // If ' FLAC' in header https://xiph.org/flac/faq.html
498 + if (check([0x7F, 0x46, 0x4C, 0x41, 0x43], {offset: 28})) {
499 + return {
500 + ext: 'oga',
501 + mime: 'audio/ogg'
502 + };
503 + }
504 +
505 + // 'Speex ' in header https://en.wikipedia.org/wiki/Speex
506 + if (check([0x53, 0x70, 0x65, 0x65, 0x78, 0x20, 0x20], {offset: 28})) {
507 + return {
508 + ext: 'spx',
509 + mime: 'audio/ogg'
510 + };
511 + }
512 +
513 + // If '\x01vorbis' in header
514 + if (check([0x01, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73], {offset: 28})) {
515 + return {
516 + ext: 'ogg',
517 + mime: 'audio/ogg'
518 + };
519 + }
520 +
521 + // Default OGG container https://www.iana.org/assignments/media-types/application/ogg
522 + return {
523 + ext: 'ogx',
524 + mime: 'application/ogg'
525 + };
526 + }
527 +
528 + if (check([0x66, 0x4C, 0x61, 0x43])) {
529 + return {
530 + ext: 'flac',
531 + mime: 'audio/x-flac'
532 + };
533 + }
534 +
535 + if (check([0x4D, 0x41, 0x43, 0x20])) { // 'MAC '
536 + return {
537 + ext: 'ape',
538 + mime: 'audio/ape'
539 + };
540 + }
541 +
542 + if (check([0x77, 0x76, 0x70, 0x6B])) { // 'wvpk'
543 + return {
544 + ext: 'wv',
545 + mime: 'audio/wavpack'
546 + };
547 + }
548 +
549 + if (check([0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A])) {
550 + return {
551 + ext: 'amr',
552 + mime: 'audio/amr'
553 + };
554 + }
555 +
556 + if (check([0x25, 0x50, 0x44, 0x46])) {
557 + return {
558 + ext: 'pdf',
559 + mime: 'application/pdf'
560 + };
561 + }
562 +
563 + if (check([0x4D, 0x5A])) {
564 + return {
565 + ext: 'exe',
566 + mime: 'application/x-msdownload'
567 + };
568 + }
569 +
570 + if (
571 + (buf[0] === 0x43 || buf[0] === 0x46) &&
572 + check([0x57, 0x53], {offset: 1})
573 + ) {
574 + return {
575 + ext: 'swf',
576 + mime: 'application/x-shockwave-flash'
577 + };
578 + }
579 +
580 + if (check([0x7B, 0x5C, 0x72, 0x74, 0x66])) {
581 + return {
582 + ext: 'rtf',
583 + mime: 'application/rtf'
584 + };
585 + }
586 +
587 + if (check([0x00, 0x61, 0x73, 0x6D])) {
588 + return {
589 + ext: 'wasm',
590 + mime: 'application/wasm'
591 + };
592 + }
593 +
594 + if (
595 + check([0x77, 0x4F, 0x46, 0x46]) &&
596 + (
597 + check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
598 + check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
599 + )
600 + ) {
601 + return {
602 + ext: 'woff',
603 + mime: 'font/woff'
604 + };
605 + }
606 +
607 + if (
608 + check([0x77, 0x4F, 0x46, 0x32]) &&
609 + (
610 + check([0x00, 0x01, 0x00, 0x00], {offset: 4}) ||
611 + check([0x4F, 0x54, 0x54, 0x4F], {offset: 4})
612 + )
613 + ) {
614 + return {
615 + ext: 'woff2',
616 + mime: 'font/woff2'
617 + };
618 + }
619 +
620 + if (
621 + check([0x4C, 0x50], {offset: 34}) &&
622 + (
623 + check([0x00, 0x00, 0x01], {offset: 8}) ||
624 + check([0x01, 0x00, 0x02], {offset: 8}) ||
625 + check([0x02, 0x00, 0x02], {offset: 8})
626 + )
627 + ) {
628 + return {
629 + ext: 'eot',
630 + mime: 'application/vnd.ms-fontobject'
631 + };
632 + }
633 +
634 + if (check([0x00, 0x01, 0x00, 0x00, 0x00])) {
635 + return {
636 + ext: 'ttf',
637 + mime: 'font/ttf'
638 + };
639 + }
640 +
641 + if (check([0x4F, 0x54, 0x54, 0x4F, 0x00])) {
642 + return {
643 + ext: 'otf',
644 + mime: 'font/otf'
645 + };
646 + }
647 +
648 + if (check([0x00, 0x00, 0x01, 0x00])) {
649 + return {
650 + ext: 'ico',
651 + mime: 'image/x-icon'
652 + };
653 + }
654 +
655 + if (check([0x00, 0x00, 0x02, 0x00])) {
656 + return {
657 + ext: 'cur',
658 + mime: 'image/x-icon'
659 + };
660 + }
661 +
662 + if (check([0x46, 0x4C, 0x56, 0x01])) {
663 + return {
664 + ext: 'flv',
665 + mime: 'video/x-flv'
666 + };
667 + }
668 +
669 + if (check([0x25, 0x21])) {
670 + return {
671 + ext: 'ps',
672 + mime: 'application/postscript'
673 + };
674 + }
675 +
676 + if (check([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) {
677 + return {
678 + ext: 'xz',
679 + mime: 'application/x-xz'
680 + };
681 + }
682 +
683 + if (check([0x53, 0x51, 0x4C, 0x69])) {
684 + return {
685 + ext: 'sqlite',
686 + mime: 'application/x-sqlite3'
687 + };
688 + }
689 +
690 + if (check([0x4E, 0x45, 0x53, 0x1A])) {
691 + return {
692 + ext: 'nes',
693 + mime: 'application/x-nintendo-nes-rom'
694 + };
695 + }
696 +
697 + if (check([0x43, 0x72, 0x32, 0x34])) {
698 + return {
699 + ext: 'crx',
700 + mime: 'application/x-google-chrome-extension'
701 + };
702 + }
703 +
704 + if (
705 + check([0x4D, 0x53, 0x43, 0x46]) ||
706 + check([0x49, 0x53, 0x63, 0x28])
707 + ) {
708 + return {
709 + ext: 'cab',
710 + mime: 'application/vnd.ms-cab-compressed'
711 + };
712 + }
713 +
714 + // Needs to be before `ar` check
715 + if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A, 0x64, 0x65, 0x62, 0x69, 0x61, 0x6E, 0x2D, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79])) {
716 + return {
717 + ext: 'deb',
718 + mime: 'application/x-deb'
719 + };
720 + }
721 +
722 + if (check([0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E])) {
723 + return {
724 + ext: 'ar',
725 + mime: 'application/x-unix-archive'
726 + };
727 + }
728 +
729 + if (check([0xED, 0xAB, 0xEE, 0xDB])) {
730 + return {
731 + ext: 'rpm',
732 + mime: 'application/x-rpm'
733 + };
734 + }
735 +
736 + if (
737 + check([0x1F, 0xA0]) ||
738 + check([0x1F, 0x9D])
739 + ) {
740 + return {
741 + ext: 'Z',
742 + mime: 'application/x-compress'
743 + };
744 + }
745 +
746 + if (check([0x4C, 0x5A, 0x49, 0x50])) {
747 + return {
748 + ext: 'lz',
749 + mime: 'application/x-lzip'
750 + };
751 + }
752 +
753 + if (check([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) {
754 + return {
755 + ext: 'msi',
756 + mime: 'application/x-msi'
757 + };
758 + }
759 +
760 + if (check([0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02])) {
761 + return {
762 + ext: 'mxf',
763 + mime: 'application/mxf'
764 + };
765 + }
766 +
767 + if (check([0x47], {offset: 4}) && (check([0x47], {offset: 192}) || check([0x47], {offset: 196}))) {
768 + return {
769 + ext: 'mts',
770 + mime: 'video/mp2t'
771 + };
772 + }
773 +
774 + if (check([0x42, 0x4C, 0x45, 0x4E, 0x44, 0x45, 0x52])) {
775 + return {
776 + ext: 'blend',
777 + mime: 'application/x-blender'
778 + };
779 + }
780 +
781 + if (check([0x42, 0x50, 0x47, 0xFB])) {
782 + return {
783 + ext: 'bpg',
784 + mime: 'image/bpg'
785 + };
786 + }
787 +
788 + if (check([0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A])) {
789 + // JPEG-2000 family
790 +
791 + if (check([0x6A, 0x70, 0x32, 0x20], {offset: 20})) {
792 + return {
793 + ext: 'jp2',
794 + mime: 'image/jp2'
795 + };
796 + }
797 +
798 + if (check([0x6A, 0x70, 0x78, 0x20], {offset: 20})) {
799 + return {
800 + ext: 'jpx',
801 + mime: 'image/jpx'
802 + };
803 + }
804 +
805 + if (check([0x6A, 0x70, 0x6D, 0x20], {offset: 20})) {
806 + return {
807 + ext: 'jpm',
808 + mime: 'image/jpm'
809 + };
810 + }
811 +
812 + if (check([0x6D, 0x6A, 0x70, 0x32], {offset: 20})) {
813 + return {
814 + ext: 'mj2',
815 + mime: 'image/mj2'
816 + };
817 + }
818 + }
819 +
820 + if (check([0x46, 0x4F, 0x52, 0x4D])) {
821 + return {
822 + ext: 'aif',
823 + mime: 'audio/aiff'
824 + };
825 + }
826 +
827 + if (checkString('<?xml ')) {
828 + return {
829 + ext: 'xml',
830 + mime: 'application/xml'
831 + };
832 + }
833 +
834 + if (check([0x42, 0x4F, 0x4F, 0x4B, 0x4D, 0x4F, 0x42, 0x49], {offset: 60})) {
835 + return {
836 + ext: 'mobi',
837 + mime: 'application/x-mobipocket-ebook'
838 + };
839 + }
840 +
841 + // File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format)
842 + if (check([0x66, 0x74, 0x79, 0x70], {offset: 4})) {
843 + if (check([0x6D, 0x69, 0x66, 0x31], {offset: 8})) {
844 + return {
845 + ext: 'heic',
846 + mime: 'image/heif'
847 + };
848 + }
849 +
850 + if (check([0x6D, 0x73, 0x66, 0x31], {offset: 8})) {
851 + return {
852 + ext: 'heic',
853 + mime: 'image/heif-sequence'
854 + };
855 + }
856 +
857 + if (check([0x68, 0x65, 0x69, 0x63], {offset: 8}) || check([0x68, 0x65, 0x69, 0x78], {offset: 8})) {
858 + return {
859 + ext: 'heic',
860 + mime: 'image/heic'
861 + };
862 + }
863 +
864 + if (check([0x68, 0x65, 0x76, 0x63], {offset: 8}) || check([0x68, 0x65, 0x76, 0x78], {offset: 8})) {
865 + return {
866 + ext: 'heic',
867 + mime: 'image/heic-sequence'
868 + };
869 + }
870 + }
871 +
872 + if (check([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) {
873 + return {
874 + ext: 'ktx',
875 + mime: 'image/ktx'
876 + };
877 + }
878 +
879 + if (check([0x44, 0x49, 0x43, 0x4D], {offset: 128})) {
880 + return {
881 + ext: 'dcm',
882 + mime: 'application/dicom'
883 + };
884 + }
885 +
886 + // Musepack, SV7
887 + if (check([0x4D, 0x50, 0x2B])) {
888 + return {
889 + ext: 'mpc',
890 + mime: 'audio/x-musepack'
891 + };
892 + }
893 +
894 + // Musepack, SV8
895 + if (check([0x4D, 0x50, 0x43, 0x4B])) {
896 + return {
897 + ext: 'mpc',
898 + mime: 'audio/x-musepack'
899 + };
900 + }
901 +
902 + if (check([0x42, 0x45, 0x47, 0x49, 0x4E, 0x3A])) {
903 + return {
904 + ext: 'ics',
905 + mime: 'text/calendar'
906 + };
907 + }
908 +
909 + if (check([0x67, 0x6C, 0x54, 0x46, 0x02, 0x00, 0x00, 0x00])) {
910 + return {
911 + ext: 'glb',
912 + mime: 'model/gltf-binary'
913 + };
914 + }
915 +
916 + if (check([0xD4, 0xC3, 0xB2, 0xA1]) || check([0xA1, 0xB2, 0xC3, 0xD4])) {
917 + return {
918 + ext: 'pcap',
919 + mime: 'application/vnd.tcpdump.pcap'
920 + };
921 + }
922 +
923 + return null;
924 +};
925 +
926 +module.exports = fileType;
927 +// TODO: Remove this for the next major release
928 +module.exports.default = fileType;
929 +
930 +Object.defineProperty(fileType, 'minimumBytes', {value: 4100});
931 +
932 +module.exports.stream = readableStream => new Promise((resolve, reject) => {
933 + // Using `eval` to work around issues when bundling with Webpack
934 + const stream = eval('require')('stream'); // eslint-disable-line no-eval
935 +
936 + readableStream.once('readable', () => {
937 + const pass = new stream.PassThrough();
938 + const chunk = readableStream.read(module.exports.minimumBytes) || readableStream.read();
939 + try {
940 + pass.fileType = fileType(chunk);
941 + } catch (error) {
942 + reject(error);
943 + }
944 +
945 + readableStream.unshift(chunk);
946 +
947 + if (stream.pipeline) {
948 + resolve(stream.pipeline(readableStream, pass, () => {}));
949 + } else {
950 + resolve(readableStream.pipe(pass));
951 + }
952 + });
953 +});
1 +MIT License
2 +
3 +Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
4 +
5 +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 +
7 +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 +
9 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1 +{
2 + "_from": "file-type@^10.10.0",
3 + "_id": "file-type@10.11.0",
4 + "_inBundle": false,
5 + "_integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==",
6 + "_location": "/image-type/file-type",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "file-type@^10.10.0",
12 + "name": "file-type",
13 + "escapedName": "file-type",
14 + "rawSpec": "^10.10.0",
15 + "saveSpec": null,
16 + "fetchSpec": "^10.10.0"
17 + },
18 + "_requiredBy": [
19 + "/image-type"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz",
22 + "_shasum": "2961d09e4675b9fb9a3ee6b69e9cd23f43fd1890",
23 + "_spec": "file-type@^10.10.0",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\image-type",
25 + "author": {
26 + "name": "Sindre Sorhus",
27 + "email": "sindresorhus@gmail.com",
28 + "url": "sindresorhus.com"
29 + },
30 + "bugs": {
31 + "url": "https://github.com/sindresorhus/file-type/issues"
32 + },
33 + "bundleDependencies": false,
34 + "deprecated": false,
35 + "description": "Detect the file type of a Buffer/Uint8Array/ArrayBuffer",
36 + "devDependencies": {
37 + "@types/node": "^11.12.2",
38 + "ava": "^1.4.1",
39 + "pify": "^4.0.1",
40 + "read-chunk": "^3.2.0",
41 + "tsd": "^0.7.1",
42 + "xo": "^0.24.0"
43 + },
44 + "engines": {
45 + "node": ">=6"
46 + },
47 + "files": [
48 + "index.js",
49 + "index.d.ts"
50 + ],
51 + "homepage": "https://github.com/sindresorhus/file-type#readme",
52 + "keywords": [
53 + "mime",
54 + "file",
55 + "type",
56 + "archive",
57 + "image",
58 + "img",
59 + "pic",
60 + "picture",
61 + "flash",
62 + "photo",
63 + "video",
64 + "detect",
65 + "check",
66 + "is",
67 + "exif",
68 + "exe",
69 + "binary",
70 + "buffer",
71 + "uint8array",
72 + "jpg",
73 + "png",
74 + "gif",
75 + "webp",
76 + "flif",
77 + "cr2",
78 + "tif",
79 + "bmp",
80 + "jxr",
81 + "psd",
82 + "zip",
83 + "tar",
84 + "rar",
85 + "gz",
86 + "bz2",
87 + "7z",
88 + "dmg",
89 + "mp4",
90 + "m4v",
91 + "mid",
92 + "mkv",
93 + "webm",
94 + "mov",
95 + "avi",
96 + "mpg",
97 + "mp2",
98 + "mp3",
99 + "m4a",
100 + "ogg",
101 + "opus",
102 + "flac",
103 + "wav",
104 + "amr",
105 + "pdf",
106 + "epub",
107 + "mobi",
108 + "swf",
109 + "rtf",
110 + "woff",
111 + "woff2",
112 + "eot",
113 + "ttf",
114 + "otf",
115 + "ico",
116 + "flv",
117 + "ps",
118 + "xz",
119 + "sqlite",
120 + "xpi",
121 + "cab",
122 + "deb",
123 + "ar",
124 + "rpm",
125 + "Z",
126 + "lz",
127 + "msi",
128 + "mxf",
129 + "mts",
130 + "wasm",
131 + "webassembly",
132 + "blend",
133 + "bpg",
134 + "docx",
135 + "pptx",
136 + "xlsx",
137 + "3gp",
138 + "jp2",
139 + "jpm",
140 + "jpx",
141 + "mj2",
142 + "aif",
143 + "odt",
144 + "ods",
145 + "odp",
146 + "xml",
147 + "heic",
148 + "wma",
149 + "ics",
150 + "glb",
151 + "pcap"
152 + ],
153 + "license": "MIT",
154 + "name": "file-type",
155 + "repository": {
156 + "type": "git",
157 + "url": "git+https://github.com/sindresorhus/file-type.git"
158 + },
159 + "scripts": {
160 + "test": "xo && ava && tsd"
161 + },
162 + "version": "10.11.0"
163 +}
1 +# file-type [![Build Status](https://travis-ci.org/sindresorhus/file-type.svg?branch=master)](https://travis-ci.org/sindresorhus/file-type)
2 +
3 +> Detect the file type of a Buffer/Uint8Array/ArrayBuffer
4 +
5 +The file type is detected by checking the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files) of the buffer.
6 +
7 +
8 +## Install
9 +
10 +```
11 +$ npm install file-type
12 +```
13 +
14 +<a href="https://www.patreon.com/sindresorhus">
15 + <img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
16 +</a>
17 +
18 +
19 +## Usage
20 +
21 +##### Node.js
22 +
23 +```js
24 +const readChunk = require('read-chunk');
25 +const fileType = require('file-type');
26 +
27 +const buffer = readChunk.sync('unicorn.png', 0, fileType.minimumBytes);
28 +
29 +fileType(buffer);
30 +//=> {ext: 'png', mime: 'image/png'}
31 +```
32 +
33 +Or from a remote location:
34 +
35 +```js
36 +const http = require('http');
37 +const fileType = require('file-type');
38 +
39 +const url = 'https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif';
40 +
41 +http.get(url, response => {
42 + response.on('readable', () => {
43 + const chunk = response.read(fileType.minimumBytes);
44 + response.destroy();
45 + console.log(fileType(chunk));
46 + //=> {ext: 'gif', mime: 'image/gif'}
47 + });
48 +});
49 +```
50 +
51 +Or from a stream:
52 +
53 +```js
54 +const fs = require('fs');
55 +const crypto = require('crypto');
56 +const fileType = require('file-type');
57 +
58 +(async () => {
59 + const read = fs.createReadStream('encrypted.enc');
60 + const decipher = crypto.createDecipheriv(alg, key, iv);
61 +
62 + const stream = await fileType.stream(read.pipe(decipher));
63 +
64 + console.log(stream.fileType);
65 + //=> {ext: 'mov', mime: 'video/quicktime'}
66 +
67 + const write = fs.createWriteStream(`decrypted.${stream.fileType.ext}`);
68 + stream.pipe(write);
69 +})();
70 +```
71 +
72 +
73 +##### Browser
74 +
75 +```js
76 +const xhr = new XMLHttpRequest();
77 +xhr.open('GET', 'unicorn.png');
78 +xhr.responseType = 'arraybuffer';
79 +
80 +xhr.onload = () => {
81 + fileType(new Uint8Array(this.response));
82 + //=> {ext: 'png', mime: 'image/png'}
83 +};
84 +
85 +xhr.send();
86 +```
87 +
88 +
89 +## API
90 +
91 +### fileType(input)
92 +
93 +Returns an `Object` with:
94 +
95 +- `ext` - One of the [supported file types](#supported-file-types)
96 +- `mime` - The [MIME type](https://en.wikipedia.org/wiki/Internet_media_type)
97 +
98 +Or `null` when there is no match.
99 +
100 +#### input
101 +
102 +Type: `Buffer | Uint8Array | ArrayBuffer`
103 +
104 +It only needs the first `.minimumBytes` bytes. The exception is detection of `docx`, `pptx`, and `xlsx` which potentially requires reading the whole file.
105 +
106 +### fileType.minimumBytes
107 +
108 +Type: `number`
109 +
110 +The minimum amount of bytes needed to detect a file type. Currently, it's 4100 bytes, but it can change, so don't hardcode it.
111 +
112 +### fileType.stream(readableStream)
113 +
114 +Detect the file type of a readable stream.
115 +
116 +Returns a `Promise` which resolves to the original readable stream argument, but with an added `fileType` property, which is an object like the one returned from `fileType()`.
117 +
118 +*Note:* This method is only for Node.js.
119 +
120 +#### readableStream
121 +
122 +Type: [`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable)
123 +
124 +
125 +## Supported file types
126 +
127 +- [`jpg`](https://en.wikipedia.org/wiki/JPEG)
128 +- [`png`](https://en.wikipedia.org/wiki/Portable_Network_Graphics)
129 +- [`gif`](https://en.wikipedia.org/wiki/GIF)
130 +- [`webp`](https://en.wikipedia.org/wiki/WebP)
131 +- [`flif`](https://en.wikipedia.org/wiki/Free_Lossless_Image_Format)
132 +- [`cr2`](https://fileinfo.com/extension/cr2)
133 +- [`tif`](https://en.wikipedia.org/wiki/Tagged_Image_File_Format)
134 +- [`bmp`](https://en.wikipedia.org/wiki/BMP_file_format)
135 +- [`jxr`](https://en.wikipedia.org/wiki/JPEG_XR)
136 +- [`psd`](https://en.wikipedia.org/wiki/Adobe_Photoshop#File_format)
137 +- [`zip`](https://en.wikipedia.org/wiki/Zip_(file_format))
138 +- [`tar`](https://en.wikipedia.org/wiki/Tar_(computing)#File_format)
139 +- [`rar`](https://en.wikipedia.org/wiki/RAR_(file_format))
140 +- [`gz`](https://en.wikipedia.org/wiki/Gzip)
141 +- [`bz2`](https://en.wikipedia.org/wiki/Bzip2)
142 +- [`7z`](https://en.wikipedia.org/wiki/7z)
143 +- [`dmg`](https://en.wikipedia.org/wiki/Apple_Disk_Image)
144 +- [`mp4`](https://en.wikipedia.org/wiki/MPEG-4_Part_14#Filename_extensions)
145 +- [`m4v`](https://en.wikipedia.org/wiki/M4V)
146 +- [`mid`](https://en.wikipedia.org/wiki/MIDI)
147 +- [`mkv`](https://en.wikipedia.org/wiki/Matroska)
148 +- [`webm`](https://en.wikipedia.org/wiki/WebM)
149 +- [`mov`](https://en.wikipedia.org/wiki/QuickTime_File_Format)
150 +- [`avi`](https://en.wikipedia.org/wiki/Audio_Video_Interleave)
151 +- [`wmv`](https://en.wikipedia.org/wiki/Windows_Media_Video)
152 +- [`mpg`](https://en.wikipedia.org/wiki/MPEG-1)
153 +- [`mp2`](https://en.wikipedia.org/wiki/MPEG-1_Audio_Layer_II)
154 +- [`mp3`](https://en.wikipedia.org/wiki/MP3)
155 +- [`m4a`](https://en.wikipedia.org/wiki/MPEG-4_Part_14#.MP4_versus_.M4A)
156 +- [`ogg`](https://en.wikipedia.org/wiki/Ogg)
157 +- [`opus`](https://en.wikipedia.org/wiki/Opus_(audio_format))
158 +- [`flac`](https://en.wikipedia.org/wiki/FLAC)
159 +- [`wav`](https://en.wikipedia.org/wiki/WAV)
160 +- [`qcp`](https://en.wikipedia.org/wiki/QCP)
161 +- [`amr`](https://en.wikipedia.org/wiki/Adaptive_Multi-Rate_audio_codec)
162 +- [`pdf`](https://en.wikipedia.org/wiki/Portable_Document_Format)
163 +- [`epub`](https://en.wikipedia.org/wiki/EPUB)
164 +- [`mobi`](https://en.wikipedia.org/wiki/Mobipocket) - Mobipocket
165 +- [`exe`](https://en.wikipedia.org/wiki/.exe)
166 +- [`swf`](https://en.wikipedia.org/wiki/SWF)
167 +- [`rtf`](https://en.wikipedia.org/wiki/Rich_Text_Format)
168 +- [`woff`](https://en.wikipedia.org/wiki/Web_Open_Font_Format)
169 +- [`woff2`](https://en.wikipedia.org/wiki/Web_Open_Font_Format)
170 +- [`eot`](https://en.wikipedia.org/wiki/Embedded_OpenType)
171 +- [`ttf`](https://en.wikipedia.org/wiki/TrueType)
172 +- [`otf`](https://en.wikipedia.org/wiki/OpenType)
173 +- [`ico`](https://en.wikipedia.org/wiki/ICO_(file_format))
174 +- [`flv`](https://en.wikipedia.org/wiki/Flash_Video)
175 +- [`ps`](https://en.wikipedia.org/wiki/Postscript)
176 +- [`xz`](https://en.wikipedia.org/wiki/Xz)
177 +- [`sqlite`](https://www.sqlite.org/fileformat2.html)
178 +- [`nes`](https://fileinfo.com/extension/nes)
179 +- [`crx`](https://developer.chrome.com/extensions/crx)
180 +- [`xpi`](https://en.wikipedia.org/wiki/XPInstall)
181 +- [`cab`](https://en.wikipedia.org/wiki/Cabinet_(file_format))
182 +- [`deb`](https://en.wikipedia.org/wiki/Deb_(file_format))
183 +- [`ar`](https://en.wikipedia.org/wiki/Ar_(Unix))
184 +- [`rpm`](https://fileinfo.com/extension/rpm)
185 +- [`Z`](https://fileinfo.com/extension/z)
186 +- [`lz`](https://en.wikipedia.org/wiki/Lzip)
187 +- [`msi`](https://en.wikipedia.org/wiki/Windows_Installer)
188 +- [`mxf`](https://en.wikipedia.org/wiki/Material_Exchange_Format)
189 +- [`mts`](https://en.wikipedia.org/wiki/.m2ts)
190 +- [`wasm`](https://en.wikipedia.org/wiki/WebAssembly)
191 +- [`blend`](https://wiki.blender.org/index.php/Dev:Source/Architecture/File_Format)
192 +- [`bpg`](https://bellard.org/bpg/)
193 +- [`docx`](https://en.wikipedia.org/wiki/Office_Open_XML)
194 +- [`pptx`](https://en.wikipedia.org/wiki/Office_Open_XML)
195 +- [`xlsx`](https://en.wikipedia.org/wiki/Office_Open_XML)
196 +- [`3gp`](https://en.wikipedia.org/wiki/3GP_and_3G2)
197 +- [`jp2`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
198 +- [`jpm`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
199 +- [`jpx`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
200 +- [`mj2`](https://en.wikipedia.org/wiki/Motion_JPEG_2000) - Motion JPEG 2000
201 +- [`aif`](https://en.wikipedia.org/wiki/Audio_Interchange_File_Format)
202 +- [`odt`](https://en.wikipedia.org/wiki/OpenDocument) - OpenDocument for word processing
203 +- [`ods`](https://en.wikipedia.org/wiki/OpenDocument) - OpenDocument for spreadsheets
204 +- [`odp`](https://en.wikipedia.org/wiki/OpenDocument) - OpenDocument for presentations
205 +- [`xml`](https://en.wikipedia.org/wiki/XML)
206 +- [`heic`](https://nokiatech.github.io/heif/technical.html)
207 +- [`cur`](https://en.wikipedia.org/wiki/ICO_(file_format))
208 +- [`ktx`](https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/)
209 +- [`ape`](https://en.wikipedia.org/wiki/Monkey%27s_Audio) - Monkey's Audio
210 +- [`wv`](https://en.wikipedia.org/wiki/WavPack) - WavPack
211 +- [`asf`](https://en.wikipedia.org/wiki/Advanced_Systems_Format) - Advanced Systems Format
212 +- [`wma`](https://en.wikipedia.org/wiki/Windows_Media_Audio) - Windows Media Audio
213 +- [`wmv`](https://en.wikipedia.org/wiki/Windows_Media_Video) - Windows Media Video
214 +- [`dcm`](https://en.wikipedia.org/wiki/DICOM#Data_format) - DICOM Image File
215 +- [`mpc`](https://en.wikipedia.org/wiki/Musepack) - Musepack (SV7 & SV8)
216 +- [`ics`](https://en.wikipedia.org/wiki/ICalendar#Data_format) - iCalendar
217 +- [`glb`](https://github.com/KhronosGroup/glTF) - GL Transmission Format
218 +- [`pcap`](https://wiki.wireshark.org/Development/LibpcapFileFormat) - Libpcap File Format
219 +
220 +*SVG isn't included as it requires the whole file to be read, but you can get it [here](https://github.com/sindresorhus/is-svg).*
221 +
222 +*Pull request welcome for additional commonly used file types.*
223 +
224 +
225 +## Related
226 +
227 +- [file-type-cli](https://github.com/sindresorhus/file-type-cli) - CLI for this module
228 +
229 +
230 +## Created by
231 +
232 +- [Sindre Sorhus](https://github.com/sindresorhus)
233 +- [Mikael Finstad](https://github.com/mifi)
234 +
235 +
236 +## License
237 +
238 +MIT
1 +{
2 + "_from": "image-type@^4.1.0",
3 + "_id": "image-type@4.1.0",
4 + "_inBundle": false,
5 + "_integrity": "sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg==",
6 + "_location": "/image-type",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "image-type@^4.1.0",
12 + "name": "image-type",
13 + "escapedName": "image-type",
14 + "rawSpec": "^4.1.0",
15 + "saveSpec": null,
16 + "fetchSpec": "^4.1.0"
17 + },
18 + "_requiredBy": [
19 + "/messaging-api-line"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/image-type/-/image-type-4.1.0.tgz",
22 + "_shasum": "72a88d64ff5021371ed67b9a466442100be57cd1",
23 + "_spec": "image-type@^4.1.0",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\messaging-api-line",
25 + "author": {
26 + "name": "Sindre Sorhus",
27 + "email": "sindresorhus@gmail.com",
28 + "url": "sindresorhus.com"
29 + },
30 + "bugs": {
31 + "url": "https://github.com/sindresorhus/image-type/issues"
32 + },
33 + "bundleDependencies": false,
34 + "dependencies": {
35 + "file-type": "^10.10.0"
36 + },
37 + "deprecated": false,
38 + "description": "Detect the image type of a Buffer/Uint8Array",
39 + "devDependencies": {
40 + "@types/node": "^11.13.0",
41 + "ava": "^1.4.1",
42 + "read-chunk": "^3.2.0",
43 + "tsd": "^0.7.2",
44 + "xo": "^0.24.0"
45 + },
46 + "engines": {
47 + "node": ">=6"
48 + },
49 + "files": [
50 + "index.js",
51 + "index.d.ts"
52 + ],
53 + "homepage": "https://github.com/sindresorhus/image-type#readme",
54 + "keywords": [
55 + "image",
56 + "img",
57 + "pic",
58 + "picture",
59 + "photo",
60 + "type",
61 + "detect",
62 + "check",
63 + "is",
64 + "exif",
65 + "binary",
66 + "buffer",
67 + "uint8array",
68 + "png",
69 + "jpg",
70 + "jpeg",
71 + "gif",
72 + "webp",
73 + "tif",
74 + "bmp",
75 + "jxr",
76 + "psd",
77 + "mime"
78 + ],
79 + "license": "MIT",
80 + "name": "image-type",
81 + "repository": {
82 + "type": "git",
83 + "url": "git+https://github.com/sindresorhus/image-type.git"
84 + },
85 + "scripts": {
86 + "test": "xo && ava && tsd"
87 + },
88 + "version": "4.1.0"
89 +}
1 +# image-type [![Build Status](https://travis-ci.org/sindresorhus/image-type.svg?branch=master)](https://travis-ci.org/sindresorhus/image-type)
2 +
3 +> Detect the image type of a Buffer/Uint8Array
4 +
5 +See the [`file-type`](https://github.com/sindresorhus/file-type) module for more file types and a CLI.
6 +
7 +
8 +## Install
9 +
10 +```
11 +$ npm install image-type
12 +```
13 +
14 +
15 +## Usage
16 +
17 +##### Node.js
18 +
19 +```js
20 +const readChunk = require('read-chunk');
21 +const imageType = require('image-type');
22 +
23 +const buffer = readChunk.sync('unicorn.png', 0, 12);
24 +
25 +imageType(buffer);
26 +//=> {ext: 'png', mime: 'image/png'}
27 +```
28 +
29 +Or from a remote location:
30 +
31 +```js
32 +const http = require('http');
33 +const imageType = require('image-type');
34 +
35 +const url = 'https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif';
36 +
37 +http.get(url, response => {
38 + response.on('readable', () => {
39 + const chunk = response.read(imageType.minimumBytes);
40 + response.destroy();
41 + console.log(imageType(chunk));
42 + //=> {ext: 'gif', mime: 'image/gif'}
43 + });
44 +});
45 +```
46 +
47 +##### Browser
48 +
49 +```js
50 +const xhr = new XMLHttpRequest();
51 +xhr.open('GET', 'unicorn.png');
52 +xhr.responseType = 'arraybuffer';
53 +
54 +xhr.onload = () => {
55 + imageType(new Uint8Array(this.response));
56 + //=> {ext: 'png', mime: 'image/png'}
57 +};
58 +
59 +xhr.send();
60 +```
61 +
62 +
63 +## API
64 +
65 +### imageType(input)
66 +
67 +Returns an `Object` with:
68 +
69 +- `ext` - One of the [supported file types](#supported-file-types)
70 +- `mime` - The [MIME type](http://en.wikipedia.org/wiki/Internet_media_type)
71 +
72 +Or `null` when there is no match.
73 +
74 +#### input
75 +
76 +Type: `Buffer | Uint8Array`
77 +
78 +It only needs the first `.minimumBytes` bytes.
79 +
80 +### imageType.minimumBytes
81 +
82 +Type: `number`
83 +
84 +The minimum amount of bytes needed to detect a file type. Currently, it's 4100 bytes, but it can change, so don't hardcode it.
85 +
86 +
87 +## Supported file types
88 +
89 +- [`jpg`](https://en.wikipedia.org/wiki/JPEG)
90 +- [`png`](https://en.wikipedia.org/wiki/Portable_Network_Graphics)
91 +- [`gif`](https://en.wikipedia.org/wiki/GIF)
92 +- [`webp`](https://en.wikipedia.org/wiki/WebP)
93 +- [`flif`](https://en.wikipedia.org/wiki/Free_Lossless_Image_Format)
94 +- [`cr2`](https://fileinfo.com/extension/cr2)
95 +- [`tif`](https://en.wikipedia.org/wiki/Tagged_Image_File_Format)
96 +- [`bmp`](https://en.wikipedia.org/wiki/BMP_file_format)
97 +- [`jxr`](https://en.wikipedia.org/wiki/JPEG_XR)
98 +- [`psd`](https://en.wikipedia.org/wiki/Adobe_Photoshop#File_format)
99 +- [`ico`](https://en.wikipedia.org/wiki/ICO_(file_format))
100 +- [`bpg`](https://bellard.org/bpg/)
101 +- [`jp2`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
102 +- [`jpm`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
103 +- [`jpx`](https://en.wikipedia.org/wiki/JPEG_2000) - JPEG 2000
104 +- [`heic`](https://nokiatech.github.io/heif/technical.html)
105 +- [`cur`](https://en.wikipedia.org/wiki/ICO_(file_format))
106 +- [`dcm`](https://en.wikipedia.org/wiki/DICOM#Data_format) - DICOM Image File
107 +
108 +*SVG isn't included as it requires the whole file to be read, but you can get it [here](https://github.com/sindresorhus/is-svg).*
109 +
110 +
111 +## License
112 +
113 +MIT © [Sindre Sorhus](https://sindresorhus.com)
1 +2.2.4 / 2018-03-13
2 +==================
3 +
4 + * Use flow strict mode (i.e. `@flow strict`).
5 +
6 +2.2.3 / 2018-02-19
7 +==================
8 +
9 + * Change license from BSD+Patents to MIT.
10 +
11 +2.2.2 / 2016-11-15
12 +==================
13 +
14 + * Add LICENSE file.
15 + * Misc housekeeping.
16 +
17 +2.2.1 / 2016-03-09
18 +==================
19 +
20 + * Use `NODE_ENV` variable instead of `__DEV__` to cache `process.env.NODE_ENV`.
21 +
22 +2.2.0 / 2015-11-17
23 +==================
24 +
25 + * Use `error.name` instead of `Invariant Violation`.
26 +
27 +2.1.3 / 2015-11-17
28 +==================
29 +
30 + * Remove `@provideModule` pragma.
31 +
32 +2.1.2 / 2015-10-27
33 +==================
34 +
35 + * Fix license.
36 +
37 +2.1.1 / 2015-09-20
38 +==================
39 +
40 + * Use correct SPDX license.
41 + * Test "browser.js" using browserify.
42 + * Switch from "envify" to "loose-envify".
43 +
44 +2.1.0 / 2015-06-03
45 +==================
46 +
47 + * Add "envify" as a dependency.
48 + * Fixed license field in "package.json".
49 +
50 +2.0.0 / 2015-02-21
51 +==================
52 +
53 + * Switch to using the "browser" field. There are now browser and server versions that respect the "format" in production.
54 +
55 +1.0.2 / 2014-09-24
56 +==================
57 +
58 + * Added tests, npmignore and gitignore.
59 + * Clarifications in README.
60 +
61 +1.0.1 / 2014-09-24
62 +==================
63 +
64 + * Actually include 'invariant.js'.
65 +
66 +1.0.0 / 2014-09-24
67 +==================
68 +
69 + * Initial release.
1 +MIT License
2 +
3 +Copyright (c) 2013-present, Facebook, Inc.
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.
1 +# invariant
2 +
3 +[![Build Status](https://travis-ci.org/zertosh/invariant.svg?branch=master)](https://travis-ci.org/zertosh/invariant)
4 +
5 +A mirror of Facebook's `invariant` (e.g. [React](https://github.com/facebook/react/blob/v0.13.3/src/vendor/core/invariant.js), [flux](https://github.com/facebook/flux/blob/2.0.2/src/invariant.js)).
6 +
7 +A way to provide descriptive errors in development but generic errors in production.
8 +
9 +## Install
10 +
11 +With [npm](http://npmjs.org) do:
12 +
13 +```sh
14 +npm install invariant
15 +```
16 +
17 +## `invariant(condition, message)`
18 +
19 +```js
20 +var invariant = require('invariant');
21 +
22 +invariant(someTruthyVal, 'This will not throw');
23 +// No errors
24 +
25 +invariant(someFalseyVal, 'This will throw an error with this message');
26 +// Error: Invariant Violation: This will throw an error with this message
27 +```
28 +
29 +**Note:** When `process.env.NODE_ENV` is not `production`, the message is required. If omitted, `invariant` will throw regardless of the truthiness of the condition. When `process.env.NODE_ENV` is `production`, the message is optional – so they can be minified away.
30 +
31 +### Browser
32 +
33 +When used with [browserify](https://github.com/substack/node-browserify), it'll use `browser.js` (instead of `invariant.js`) and the [envify](https://github.com/hughsk/envify) transform will inline the value of `process.env.NODE_ENV`.
34 +
35 +### Node
36 +
37 +The node version is optimized around the performance implications of accessing `process.env`. The value of `process.env.NODE_ENV` is cached, and repeatedly used instead of reading `process.env`. See [Server rendering is slower with npm react #812](https://github.com/facebook/react/issues/812)
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +'use strict';
9 +
10 +/**
11 + * Use invariant() to assert state which your program assumes to be true.
12 + *
13 + * Provide sprintf-style format (only %s is supported) and arguments
14 + * to provide information about what broke and what you were
15 + * expecting.
16 + *
17 + * The invariant message will be stripped in production, but the invariant
18 + * will remain to ensure logic does not differ in production.
19 + */
20 +
21 +var invariant = function(condition, format, a, b, c, d, e, f) {
22 + if (process.env.NODE_ENV !== 'production') {
23 + if (format === undefined) {
24 + throw new Error('invariant requires an error message argument');
25 + }
26 + }
27 +
28 + if (!condition) {
29 + var error;
30 + if (format === undefined) {
31 + error = new Error(
32 + 'Minified exception occurred; use the non-minified dev environment ' +
33 + 'for the full error message and additional helpful warnings.'
34 + );
35 + } else {
36 + var args = [a, b, c, d, e, f];
37 + var argIndex = 0;
38 + error = new Error(
39 + format.replace(/%s/g, function() { return args[argIndex++]; })
40 + );
41 + error.name = 'Invariant Violation';
42 + }
43 +
44 + error.framesToPop = 1; // we don't care about invariant's own frame
45 + throw error;
46 + }
47 +};
48 +
49 +module.exports = invariant;
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +'use strict';
9 +
10 +/**
11 + * Use invariant() to assert state which your program assumes to be true.
12 + *
13 + * Provide sprintf-style format (only %s is supported) and arguments
14 + * to provide information about what broke and what you were
15 + * expecting.
16 + *
17 + * The invariant message will be stripped in production, but the invariant
18 + * will remain to ensure logic does not differ in production.
19 + */
20 +
21 +var NODE_ENV = process.env.NODE_ENV;
22 +
23 +var invariant = function(condition, format, a, b, c, d, e, f) {
24 + if (NODE_ENV !== 'production') {
25 + if (format === undefined) {
26 + throw new Error('invariant requires an error message argument');
27 + }
28 + }
29 +
30 + if (!condition) {
31 + var error;
32 + if (format === undefined) {
33 + error = new Error(
34 + 'Minified exception occurred; use the non-minified dev environment ' +
35 + 'for the full error message and additional helpful warnings.'
36 + );
37 + } else {
38 + var args = [a, b, c, d, e, f];
39 + var argIndex = 0;
40 + error = new Error(
41 + format.replace(/%s/g, function() { return args[argIndex++]; })
42 + );
43 + error.name = 'Invariant Violation';
44 + }
45 +
46 + error.framesToPop = 1; // we don't care about invariant's own frame
47 + throw error;
48 + }
49 +};
50 +
51 +module.exports = invariant;
1 +/* @flow strict */
2 +
3 +declare module.exports: (
4 + condition: any,
5 + format?: string,
6 + ...args: Array<any>
7 +) => void;
1 +{
2 + "_from": "invariant@^2.2.4",
3 + "_id": "invariant@2.2.4",
4 + "_inBundle": false,
5 + "_integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
6 + "_location": "/invariant",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "invariant@^2.2.4",
12 + "name": "invariant",
13 + "escapedName": "invariant",
14 + "rawSpec": "^2.2.4",
15 + "saveSpec": null,
16 + "fetchSpec": "^2.2.4"
17 + },
18 + "_requiredBy": [
19 + "/messaging-api-line"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
22 + "_shasum": "610f3c92c9359ce1db616e538008d23ff35158e6",
23 + "_spec": "invariant@^2.2.4",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\messaging-api-line",
25 + "author": {
26 + "name": "Andres Suarez",
27 + "email": "zertosh@gmail.com"
28 + },
29 + "browser": "browser.js",
30 + "browserify": {
31 + "transform": [
32 + "loose-envify"
33 + ]
34 + },
35 + "bugs": {
36 + "url": "https://github.com/zertosh/invariant/issues"
37 + },
38 + "bundleDependencies": false,
39 + "dependencies": {
40 + "loose-envify": "^1.0.0"
41 + },
42 + "deprecated": false,
43 + "description": "invariant",
44 + "devDependencies": {
45 + "browserify": "^11.0.1",
46 + "flow-bin": "^0.67.1",
47 + "tap": "^1.4.0"
48 + },
49 + "files": [
50 + "browser.js",
51 + "invariant.js",
52 + "invariant.js.flow"
53 + ],
54 + "homepage": "https://github.com/zertosh/invariant#readme",
55 + "keywords": [
56 + "test",
57 + "invariant"
58 + ],
59 + "license": "MIT",
60 + "main": "invariant.js",
61 + "name": "invariant",
62 + "repository": {
63 + "type": "git",
64 + "url": "git+https://github.com/zertosh/invariant.git"
65 + },
66 + "scripts": {
67 + "test": "NODE_ENV=production tap test/*.js && NODE_ENV=development tap test/*.js"
68 + },
69 + "version": "2.2.4"
70 +}
1 +### Version 4.0.0 (2018-01-28) ###
2 +
3 +- Added: Support for ES2018. The only change needed was recognizing the `s`
4 + regex flag.
5 +- Changed: _All_ tokens returned by the `matchToToken` function now have a
6 + `closed` property. It is set to `undefined` for the tokens where “closed”
7 + doesn’t make sense. This means that all tokens objects have the same shape,
8 + which might improve performance.
9 +
10 +These are the breaking changes:
11 +
12 +- `'/a/s'.match(jsTokens)` no longer returns `['/', 'a', '/', 's']`, but
13 + `['/a/s']`. (There are of course other variations of this.)
14 +- Code that rely on some token objects not having the `closed` property could
15 + now behave differently.
16 +
17 +
18 +### Version 3.0.2 (2017-06-28) ###
19 +
20 +- No code changes. Just updates to the readme.
21 +
22 +
23 +### Version 3.0.1 (2017-01-30) ###
24 +
25 +- Fixed: ES2015 unicode escapes with more than 6 hex digits are now matched
26 + correctly.
27 +
28 +
29 +### Version 3.0.0 (2017-01-11) ###
30 +
31 +This release contains one breaking change, that should [improve performance in
32 +V8][v8-perf]:
33 +
34 +> So how can you, as a JavaScript developer, ensure that your RegExps are fast?
35 +> If you are not interested in hooking into RegExp internals, make sure that
36 +> neither the RegExp instance, nor its prototype is modified in order to get the
37 +> best performance:
38 +>
39 +> ```js
40 +> var re = /./g;
41 +> re.exec(''); // Fast path.
42 +> re.new_property = 'slow';
43 +> ```
44 +
45 +This module used to export a single regex, with `.matchToToken` bolted
46 +on, just like in the above example. This release changes the exports of
47 +the module to avoid this issue.
48 +
49 +Before:
50 +
51 +```js
52 +import jsTokens from "js-tokens"
53 +// or:
54 +var jsTokens = require("js-tokens")
55 +var matchToToken = jsTokens.matchToToken
56 +```
57 +
58 +After:
59 +
60 +```js
61 +import jsTokens, {matchToToken} from "js-tokens"
62 +// or:
63 +var jsTokens = require("js-tokens").default
64 +var matchToToken = require("js-tokens").matchToToken
65 +```
66 +
67 +[v8-perf]: http://v8project.blogspot.se/2017/01/speeding-up-v8-regular-expressions.html
68 +
69 +
70 +### Version 2.0.0 (2016-06-19) ###
71 +
72 +- Added: Support for ES2016. In other words, support for the `**` exponentiation
73 + operator.
74 +
75 +These are the breaking changes:
76 +
77 +- `'**'.match(jsTokens)` no longer returns `['*', '*']`, but `['**']`.
78 +- `'**='.match(jsTokens)` no longer returns `['*', '*=']`, but `['**=']`.
79 +
80 +
81 +### Version 1.0.3 (2016-03-27) ###
82 +
83 +- Improved: Made the regex ever so slightly smaller.
84 +- Updated: The readme.
85 +
86 +
87 +### Version 1.0.2 (2015-10-18) ###
88 +
89 +- Improved: Limited npm package contents for a smaller download. Thanks to
90 + @zertosh!
91 +
92 +
93 +### Version 1.0.1 (2015-06-20) ###
94 +
95 +- Fixed: Declared an undeclared variable.
96 +
97 +
98 +### Version 1.0.0 (2015-02-26) ###
99 +
100 +- Changed: Merged the 'operator' and 'punctuation' types into 'punctuator'. That
101 + type is now equivalent to the Punctuator token in the ECMAScript
102 + specification. (Backwards-incompatible change.)
103 +- Fixed: A `-` followed by a number is now correctly matched as a punctuator
104 + followed by a number. It used to be matched as just a number, but there is no
105 + such thing as negative number literals. (Possibly backwards-incompatible
106 + change.)
107 +
108 +
109 +### Version 0.4.1 (2015-02-21) ###
110 +
111 +- Added: Support for the regex `u` flag.
112 +
113 +
114 +### Version 0.4.0 (2015-02-21) ###
115 +
116 +- Improved: `jsTokens.matchToToken` performance.
117 +- Added: Support for octal and binary number literals.
118 +- Added: Support for template strings.
119 +
120 +
121 +### Version 0.3.1 (2015-01-06) ###
122 +
123 +- Fixed: Support for unicode spaces. They used to be allowed in names (which is
124 + very confusing), and some unicode newlines were wrongly allowed in strings and
125 + regexes.
126 +
127 +
128 +### Version 0.3.0 (2014-12-19) ###
129 +
130 +- Changed: The `jsTokens.names` array has been replaced with the
131 + `jsTokens.matchToToken` function. The capturing groups of `jsTokens` are no
132 + longer part of the public API; instead use said function. See this [gist] for
133 + an example. (Backwards-incompatible change.)
134 +- Changed: The empty string is now considered an “invalid” token, instead an
135 + “empty” token (its own group). (Backwards-incompatible change.)
136 +- Removed: component support. (Backwards-incompatible change.)
137 +
138 +[gist]: https://gist.github.com/lydell/be49dbf80c382c473004
139 +
140 +
141 +### Version 0.2.0 (2014-06-19) ###
142 +
143 +- Changed: Match ES6 function arrows (`=>`) as an operator, instead of its own
144 + category (“functionArrow”), for simplicity. (Backwards-incompatible change.)
145 +- Added: ES6 splats (`...`) are now matched as an operator (instead of three
146 + punctuations). (Backwards-incompatible change.)
147 +
148 +
149 +### Version 0.1.0 (2014-03-08) ###
150 +
151 +- Initial release.
1 +The MIT License (MIT)
2 +
3 +Copyright (c) 2014, 2015, 2016, 2017, 2018 Simon Lydell
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
13 +all 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
21 +THE SOFTWARE.
1 +Overview [![Build Status](https://travis-ci.org/lydell/js-tokens.svg?branch=master)](https://travis-ci.org/lydell/js-tokens)
2 +========
3 +
4 +A regex that tokenizes JavaScript.
5 +
6 +```js
7 +var jsTokens = require("js-tokens").default
8 +
9 +var jsString = "var foo=opts.foo;\n..."
10 +
11 +jsString.match(jsTokens)
12 +// ["var", " ", "foo", "=", "opts", ".", "foo", ";", "\n", ...]
13 +```
14 +
15 +
16 +Installation
17 +============
18 +
19 +`npm install js-tokens`
20 +
21 +```js
22 +import jsTokens from "js-tokens"
23 +// or:
24 +var jsTokens = require("js-tokens").default
25 +```
26 +
27 +
28 +Usage
29 +=====
30 +
31 +### `jsTokens` ###
32 +
33 +A regex with the `g` flag that matches JavaScript tokens.
34 +
35 +The regex _always_ matches, even invalid JavaScript and the empty string.
36 +
37 +The next match is always directly after the previous.
38 +
39 +### `var token = matchToToken(match)` ###
40 +
41 +```js
42 +import {matchToToken} from "js-tokens"
43 +// or:
44 +var matchToToken = require("js-tokens").matchToToken
45 +```
46 +
47 +Takes a `match` returned by `jsTokens.exec(string)`, and returns a `{type:
48 +String, value: String}` object. The following types are available:
49 +
50 +- string
51 +- comment
52 +- regex
53 +- number
54 +- name
55 +- punctuator
56 +- whitespace
57 +- invalid
58 +
59 +Multi-line comments and strings also have a `closed` property indicating if the
60 +token was closed or not (see below).
61 +
62 +Comments and strings both come in several flavors. To distinguish them, check if
63 +the token starts with `//`, `/*`, `'`, `"` or `` ` ``.
64 +
65 +Names are ECMAScript IdentifierNames, that is, including both identifiers and
66 +keywords. You may use [is-keyword-js] to tell them apart.
67 +
68 +Whitespace includes both line terminators and other whitespace.
69 +
70 +[is-keyword-js]: https://github.com/crissdev/is-keyword-js
71 +
72 +
73 +ECMAScript support
74 +==================
75 +
76 +The intention is to always support the latest ECMAScript version whose feature
77 +set has been finalized.
78 +
79 +If adding support for a newer version requires changes, a new version with a
80 +major verion bump will be released.
81 +
82 +Currently, ECMAScript 2018 is supported.
83 +
84 +
85 +Invalid code handling
86 +=====================
87 +
88 +Unterminated strings are still matched as strings. JavaScript strings cannot
89 +contain (unescaped) newlines, so unterminated strings simply end at the end of
90 +the line. Unterminated template strings can contain unescaped newlines, though,
91 +so they go on to the end of input.
92 +
93 +Unterminated multi-line comments are also still matched as comments. They
94 +simply go on to the end of the input.
95 +
96 +Unterminated regex literals are likely matched as division and whatever is
97 +inside the regex.
98 +
99 +Invalid ASCII characters have their own capturing group.
100 +
101 +Invalid non-ASCII characters are treated as names, to simplify the matching of
102 +names (except unicode spaces which are treated as whitespace). Note: See also
103 +the [ES2018](#es2018) section.
104 +
105 +Regex literals may contain invalid regex syntax. They are still matched as
106 +regex literals. They may also contain repeated regex flags, to keep the regex
107 +simple.
108 +
109 +Strings may contain invalid escape sequences.
110 +
111 +
112 +Limitations
113 +===========
114 +
115 +Tokenizing JavaScript using regexes—in fact, _one single regex_—won’t be
116 +perfect. But that’s not the point either.
117 +
118 +You may compare jsTokens with [esprima] by using `esprima-compare.js`.
119 +See `npm run esprima-compare`!
120 +
121 +[esprima]: http://esprima.org/
122 +
123 +### Template string interpolation ###
124 +
125 +Template strings are matched as single tokens, from the starting `` ` `` to the
126 +ending `` ` ``, including interpolations (whose tokens are not matched
127 +individually).
128 +
129 +Matching template string interpolations requires recursive balancing of `{` and
130 +`}`—something that JavaScript regexes cannot do. Only one level of nesting is
131 +supported.
132 +
133 +### Division and regex literals collision ###
134 +
135 +Consider this example:
136 +
137 +```js
138 +var g = 9.82
139 +var number = bar / 2/g
140 +
141 +var regex = / 2/g
142 +```
143 +
144 +A human can easily understand that in the `number` line we’re dealing with
145 +division, and in the `regex` line we’re dealing with a regex literal. How come?
146 +Because humans can look at the whole code to put the `/` characters in context.
147 +A JavaScript regex cannot. It only sees forwards. (Well, ES2018 regexes can also
148 +look backwards. See the [ES2018](#es2018) section).
149 +
150 +When the `jsTokens` regex scans throught the above, it will see the following
151 +at the end of both the `number` and `regex` rows:
152 +
153 +```js
154 +/ 2/g
155 +```
156 +
157 +It is then impossible to know if that is a regex literal, or part of an
158 +expression dealing with division.
159 +
160 +Here is a similar case:
161 +
162 +```js
163 +foo /= 2/g
164 +foo(/= 2/g)
165 +```
166 +
167 +The first line divides the `foo` variable with `2/g`. The second line calls the
168 +`foo` function with the regex literal `/= 2/g`. Again, since `jsTokens` only
169 +sees forwards, it cannot tell the two cases apart.
170 +
171 +There are some cases where we _can_ tell division and regex literals apart,
172 +though.
173 +
174 +First off, we have the simple cases where there’s only one slash in the line:
175 +
176 +```js
177 +var foo = 2/g
178 +foo /= 2
179 +```
180 +
181 +Regex literals cannot contain newlines, so the above cases are correctly
182 +identified as division. Things are only problematic when there are more than
183 +one non-comment slash in a single line.
184 +
185 +Secondly, not every character is a valid regex flag.
186 +
187 +```js
188 +var number = bar / 2/e
189 +```
190 +
191 +The above example is also correctly identified as division, because `e` is not a
192 +valid regex flag. I initially wanted to future-proof by allowing `[a-zA-Z]*`
193 +(any letter) as flags, but it is not worth it since it increases the amount of
194 +ambigous cases. So only the standard `g`, `m`, `i`, `y` and `u` flags are
195 +allowed. This means that the above example will be identified as division as
196 +long as you don’t rename the `e` variable to some permutation of `gmiyus` 1 to 6
197 +characters long.
198 +
199 +Lastly, we can look _forward_ for information.
200 +
201 +- If the token following what looks like a regex literal is not valid after a
202 + regex literal, but is valid in a division expression, then the regex literal
203 + is treated as division instead. For example, a flagless regex cannot be
204 + followed by a string, number or name, but all of those three can be the
205 + denominator of a division.
206 +- Generally, if what looks like a regex literal is followed by an operator, the
207 + regex literal is treated as division instead. This is because regexes are
208 + seldomly used with operators (such as `+`, `*`, `&&` and `==`), but division
209 + could likely be part of such an expression.
210 +
211 +Please consult the regex source and the test cases for precise information on
212 +when regex or division is matched (should you need to know). In short, you
213 +could sum it up as:
214 +
215 +If the end of a statement looks like a regex literal (even if it isn’t), it
216 +will be treated as one. Otherwise it should work as expected (if you write sane
217 +code).
218 +
219 +### ES2018 ###
220 +
221 +ES2018 added some nice regex improvements to the language.
222 +
223 +- [Unicode property escapes] should allow telling names and invalid non-ASCII
224 + characters apart without blowing up the regex size.
225 +- [Lookbehind assertions] should allow matching telling division and regex
226 + literals apart in more cases.
227 +- [Named capture groups] might simplify some things.
228 +
229 +These things would be nice to do, but are not critical. They probably have to
230 +wait until the oldest maintained Node.js LTS release supports those features.
231 +
232 +[Unicode property escapes]: http://2ality.com/2017/07/regexp-unicode-property-escapes.html
233 +[Lookbehind assertions]: http://2ality.com/2017/05/regexp-lookbehind-assertions.html
234 +[Named capture groups]: http://2ality.com/2017/05/regexp-named-capture-groups.html
235 +
236 +
237 +License
238 +=======
239 +
240 +[MIT](LICENSE).
1 +// Copyright 2014, 2015, 2016, 2017, 2018 Simon Lydell
2 +// License: MIT. (See LICENSE.)
3 +
4 +Object.defineProperty(exports, "__esModule", {
5 + value: true
6 +})
7 +
8 +// This regex comes from regex.coffee, and is inserted here by generate-index.js
9 +// (run `npm run build`).
10 +exports.default = /((['"])(?:(?!\2|\\).|\\(?:\r\n|[\s\S]))*(\2)?|`(?:[^`\\$]|\\[\s\S]|\$(?!\{)|\$\{(?:[^{}]|\{[^}]*\}?)*\}?)*(`)?)|(\/\/.*)|(\/\*(?:[^*]|\*(?!\/))*(\*\/)?)|(\/(?!\*)(?:\[(?:(?![\]\\]).|\\.)*\]|(?![\/\]\\]).|\\.)+\/(?:(?!\s*(?:\b|[\u0080-\uFFFF$\\'"~({]|[+\-!](?!=)|\.?\d))|[gmiyus]{1,6}\b(?![\u0080-\uFFFF$\\]|\s*(?:[+\-*%&|^<>!=?({]|\/(?![\/*])))))|(0[xX][\da-fA-F]+|0[oO][0-7]+|0[bB][01]+|(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)|((?!\d)(?:(?!\s)[$\w\u0080-\uFFFF]|\\u[\da-fA-F]{4}|\\u\{[\da-fA-F]+\})+)|(--|\+\+|&&|\|\||=>|\.{3}|(?:[+\-\/%&|^]|\*{1,2}|<{1,2}|>{1,3}|!=?|={1,2})=?|[?~.,:;[\](){}])|(\s+)|(^$|[\s\S])/g
11 +
12 +exports.matchToToken = function(match) {
13 + var token = {type: "invalid", value: match[0], closed: undefined}
14 + if (match[ 1]) token.type = "string" , token.closed = !!(match[3] || match[4])
15 + else if (match[ 5]) token.type = "comment"
16 + else if (match[ 6]) token.type = "comment", token.closed = !!match[7]
17 + else if (match[ 8]) token.type = "regex"
18 + else if (match[ 9]) token.type = "number"
19 + else if (match[10]) token.type = "name"
20 + else if (match[11]) token.type = "punctuator"
21 + else if (match[12]) token.type = "whitespace"
22 + return token
23 +}
1 +{
2 + "_from": "js-tokens@^3.0.0 || ^4.0.0",
3 + "_id": "js-tokens@4.0.0",
4 + "_inBundle": false,
5 + "_integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
6 + "_location": "/js-tokens",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "js-tokens@^3.0.0 || ^4.0.0",
12 + "name": "js-tokens",
13 + "escapedName": "js-tokens",
14 + "rawSpec": "^3.0.0 || ^4.0.0",
15 + "saveSpec": null,
16 + "fetchSpec": "^3.0.0 || ^4.0.0"
17 + },
18 + "_requiredBy": [
19 + "/loose-envify"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
22 + "_shasum": "19203fb59991df98e3a287050d4647cdeaf32499",
23 + "_spec": "js-tokens@^3.0.0 || ^4.0.0",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\loose-envify",
25 + "author": {
26 + "name": "Simon Lydell"
27 + },
28 + "bugs": {
29 + "url": "https://github.com/lydell/js-tokens/issues"
30 + },
31 + "bundleDependencies": false,
32 + "deprecated": false,
33 + "description": "A regex that tokenizes JavaScript.",
34 + "devDependencies": {
35 + "coffeescript": "2.1.1",
36 + "esprima": "4.0.0",
37 + "everything.js": "1.0.3",
38 + "mocha": "5.0.0"
39 + },
40 + "files": [
41 + "index.js"
42 + ],
43 + "homepage": "https://github.com/lydell/js-tokens#readme",
44 + "keywords": [
45 + "JavaScript",
46 + "js",
47 + "token",
48 + "tokenize",
49 + "regex"
50 + ],
51 + "license": "MIT",
52 + "name": "js-tokens",
53 + "repository": {
54 + "type": "git",
55 + "url": "git+https://github.com/lydell/js-tokens.git"
56 + },
57 + "scripts": {
58 + "build": "node generate-index.js",
59 + "dev": "npm run build && npm test",
60 + "esprima-compare": "node esprima-compare ./index.js everything.js/es5.js",
61 + "test": "mocha --ui tdd"
62 + },
63 + "version": "4.0.0"
64 +}
1 +Copyright jQuery Foundation and other contributors <https://jquery.org/>
2 +
3 +Based on Underscore.js, copyright Jeremy Ashkenas,
4 +DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
5 +
6 +This software consists of voluntary contributions made by many
7 +individuals. For exact contribution history, see the revision history
8 +available at https://github.com/lodash/lodash
9 +
10 +The following license applies to all parts of this software except as
11 +documented below:
12 +
13 +====
14 +
15 +Permission is hereby granted, free of charge, to any person obtaining
16 +a copy of this software and associated documentation files (the
17 +"Software"), to deal in the Software without restriction, including
18 +without limitation the rights to use, copy, modify, merge, publish,
19 +distribute, sublicense, and/or sell copies of the Software, and to
20 +permit persons to whom the Software is furnished to do so, subject to
21 +the following conditions:
22 +
23 +The above copyright notice and this permission notice shall be
24 +included in all copies or substantial portions of the Software.
25 +
26 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 +
34 +====
35 +
36 +Copyright and related rights for sample code are waived via CC0. Sample
37 +code is defined as all source code displayed within the prose of the
38 +documentation.
39 +
40 +CC0: http://creativecommons.org/publicdomain/zero/1.0/
41 +
42 +====
43 +
44 +Files located in the node_modules and vendor directories are externally
45 +maintained libraries used by this software which have their own
46 +licenses; we recommend you read them, as their terms may differ from the
47 +terms above.
1 +# lodash.omit v4.5.0
2 +
3 +The [lodash](https://lodash.com/) method `_.omit` exported as a [Node.js](https://nodejs.org/) module.
4 +
5 +## Installation
6 +
7 +Using npm:
8 +```bash
9 +$ {sudo -H} npm i -g npm
10 +$ npm i --save lodash.omit
11 +```
12 +
13 +In Node.js:
14 +```js
15 +var omit = require('lodash.omit');
16 +```
17 +
18 +See the [documentation](https://lodash.com/docs#omit) or [package source](https://github.com/lodash/lodash/blob/4.5.0-npm-packages/lodash.omit) for more details.
1 +/**
2 + * lodash (Custom Build) <https://lodash.com/>
3 + * Build: `lodash modularize exports="npm" -o ./`
4 + * Copyright jQuery Foundation and other contributors <https://jquery.org/>
5 + * Released under MIT license <https://lodash.com/license>
6 + * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
7 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
8 + */
9 +
10 +/** Used as the size to enable large array optimizations. */
11 +var LARGE_ARRAY_SIZE = 200;
12 +
13 +/** Used to stand-in for `undefined` hash values. */
14 +var HASH_UNDEFINED = '__lodash_hash_undefined__';
15 +
16 +/** Used as references for various `Number` constants. */
17 +var INFINITY = 1 / 0,
18 + MAX_SAFE_INTEGER = 9007199254740991;
19 +
20 +/** `Object#toString` result references. */
21 +var argsTag = '[object Arguments]',
22 + funcTag = '[object Function]',
23 + genTag = '[object GeneratorFunction]',
24 + symbolTag = '[object Symbol]';
25 +
26 +/**
27 + * Used to match `RegExp`
28 + * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
29 + */
30 +var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
31 +
32 +/** Used to detect host constructors (Safari). */
33 +var reIsHostCtor = /^\[object .+?Constructor\]$/;
34 +
35 +/** Used to detect unsigned integer values. */
36 +var reIsUint = /^(?:0|[1-9]\d*)$/;
37 +
38 +/** Detect free variable `global` from Node.js. */
39 +var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
40 +
41 +/** Detect free variable `self`. */
42 +var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
43 +
44 +/** Used as a reference to the global object. */
45 +var root = freeGlobal || freeSelf || Function('return this')();
46 +
47 +/**
48 + * A faster alternative to `Function#apply`, this function invokes `func`
49 + * with the `this` binding of `thisArg` and the arguments of `args`.
50 + *
51 + * @private
52 + * @param {Function} func The function to invoke.
53 + * @param {*} thisArg The `this` binding of `func`.
54 + * @param {Array} args The arguments to invoke `func` with.
55 + * @returns {*} Returns the result of `func`.
56 + */
57 +function apply(func, thisArg, args) {
58 + switch (args.length) {
59 + case 0: return func.call(thisArg);
60 + case 1: return func.call(thisArg, args[0]);
61 + case 2: return func.call(thisArg, args[0], args[1]);
62 + case 3: return func.call(thisArg, args[0], args[1], args[2]);
63 + }
64 + return func.apply(thisArg, args);
65 +}
66 +
67 +/**
68 + * A specialized version of `_.includes` for arrays without support for
69 + * specifying an index to search from.
70 + *
71 + * @private
72 + * @param {Array} [array] The array to inspect.
73 + * @param {*} target The value to search for.
74 + * @returns {boolean} Returns `true` if `target` is found, else `false`.
75 + */
76 +function arrayIncludes(array, value) {
77 + var length = array ? array.length : 0;
78 + return !!length && baseIndexOf(array, value, 0) > -1;
79 +}
80 +
81 +/**
82 + * This function is like `arrayIncludes` except that it accepts a comparator.
83 + *
84 + * @private
85 + * @param {Array} [array] The array to inspect.
86 + * @param {*} target The value to search for.
87 + * @param {Function} comparator The comparator invoked per element.
88 + * @returns {boolean} Returns `true` if `target` is found, else `false`.
89 + */
90 +function arrayIncludesWith(array, value, comparator) {
91 + var index = -1,
92 + length = array ? array.length : 0;
93 +
94 + while (++index < length) {
95 + if (comparator(value, array[index])) {
96 + return true;
97 + }
98 + }
99 + return false;
100 +}
101 +
102 +/**
103 + * A specialized version of `_.map` for arrays without support for iteratee
104 + * shorthands.
105 + *
106 + * @private
107 + * @param {Array} [array] The array to iterate over.
108 + * @param {Function} iteratee The function invoked per iteration.
109 + * @returns {Array} Returns the new mapped array.
110 + */
111 +function arrayMap(array, iteratee) {
112 + var index = -1,
113 + length = array ? array.length : 0,
114 + result = Array(length);
115 +
116 + while (++index < length) {
117 + result[index] = iteratee(array[index], index, array);
118 + }
119 + return result;
120 +}
121 +
122 +/**
123 + * Appends the elements of `values` to `array`.
124 + *
125 + * @private
126 + * @param {Array} array The array to modify.
127 + * @param {Array} values The values to append.
128 + * @returns {Array} Returns `array`.
129 + */
130 +function arrayPush(array, values) {
131 + var index = -1,
132 + length = values.length,
133 + offset = array.length;
134 +
135 + while (++index < length) {
136 + array[offset + index] = values[index];
137 + }
138 + return array;
139 +}
140 +
141 +/**
142 + * The base implementation of `_.findIndex` and `_.findLastIndex` without
143 + * support for iteratee shorthands.
144 + *
145 + * @private
146 + * @param {Array} array The array to inspect.
147 + * @param {Function} predicate The function invoked per iteration.
148 + * @param {number} fromIndex The index to search from.
149 + * @param {boolean} [fromRight] Specify iterating from right to left.
150 + * @returns {number} Returns the index of the matched value, else `-1`.
151 + */
152 +function baseFindIndex(array, predicate, fromIndex, fromRight) {
153 + var length = array.length,
154 + index = fromIndex + (fromRight ? 1 : -1);
155 +
156 + while ((fromRight ? index-- : ++index < length)) {
157 + if (predicate(array[index], index, array)) {
158 + return index;
159 + }
160 + }
161 + return -1;
162 +}
163 +
164 +/**
165 + * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
166 + *
167 + * @private
168 + * @param {Array} array The array to inspect.
169 + * @param {*} value The value to search for.
170 + * @param {number} fromIndex The index to search from.
171 + * @returns {number} Returns the index of the matched value, else `-1`.
172 + */
173 +function baseIndexOf(array, value, fromIndex) {
174 + if (value !== value) {
175 + return baseFindIndex(array, baseIsNaN, fromIndex);
176 + }
177 + var index = fromIndex - 1,
178 + length = array.length;
179 +
180 + while (++index < length) {
181 + if (array[index] === value) {
182 + return index;
183 + }
184 + }
185 + return -1;
186 +}
187 +
188 +/**
189 + * The base implementation of `_.isNaN` without support for number objects.
190 + *
191 + * @private
192 + * @param {*} value The value to check.
193 + * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
194 + */
195 +function baseIsNaN(value) {
196 + return value !== value;
197 +}
198 +
199 +/**
200 + * The base implementation of `_.times` without support for iteratee shorthands
201 + * or max array length checks.
202 + *
203 + * @private
204 + * @param {number} n The number of times to invoke `iteratee`.
205 + * @param {Function} iteratee The function invoked per iteration.
206 + * @returns {Array} Returns the array of results.
207 + */
208 +function baseTimes(n, iteratee) {
209 + var index = -1,
210 + result = Array(n);
211 +
212 + while (++index < n) {
213 + result[index] = iteratee(index);
214 + }
215 + return result;
216 +}
217 +
218 +/**
219 + * The base implementation of `_.unary` without support for storing metadata.
220 + *
221 + * @private
222 + * @param {Function} func The function to cap arguments for.
223 + * @returns {Function} Returns the new capped function.
224 + */
225 +function baseUnary(func) {
226 + return function(value) {
227 + return func(value);
228 + };
229 +}
230 +
231 +/**
232 + * Checks if a cache value for `key` exists.
233 + *
234 + * @private
235 + * @param {Object} cache The cache to query.
236 + * @param {string} key The key of the entry to check.
237 + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
238 + */
239 +function cacheHas(cache, key) {
240 + return cache.has(key);
241 +}
242 +
243 +/**
244 + * Gets the value at `key` of `object`.
245 + *
246 + * @private
247 + * @param {Object} [object] The object to query.
248 + * @param {string} key The key of the property to get.
249 + * @returns {*} Returns the property value.
250 + */
251 +function getValue(object, key) {
252 + return object == null ? undefined : object[key];
253 +}
254 +
255 +/**
256 + * Checks if `value` is a host object in IE < 9.
257 + *
258 + * @private
259 + * @param {*} value The value to check.
260 + * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
261 + */
262 +function isHostObject(value) {
263 + // Many host objects are `Object` objects that can coerce to strings
264 + // despite having improperly defined `toString` methods.
265 + var result = false;
266 + if (value != null && typeof value.toString != 'function') {
267 + try {
268 + result = !!(value + '');
269 + } catch (e) {}
270 + }
271 + return result;
272 +}
273 +
274 +/**
275 + * Creates a unary function that invokes `func` with its argument transformed.
276 + *
277 + * @private
278 + * @param {Function} func The function to wrap.
279 + * @param {Function} transform The argument transform.
280 + * @returns {Function} Returns the new function.
281 + */
282 +function overArg(func, transform) {
283 + return function(arg) {
284 + return func(transform(arg));
285 + };
286 +}
287 +
288 +/** Used for built-in method references. */
289 +var arrayProto = Array.prototype,
290 + funcProto = Function.prototype,
291 + objectProto = Object.prototype;
292 +
293 +/** Used to detect overreaching core-js shims. */
294 +var coreJsData = root['__core-js_shared__'];
295 +
296 +/** Used to detect methods masquerading as native. */
297 +var maskSrcKey = (function() {
298 + var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
299 + return uid ? ('Symbol(src)_1.' + uid) : '';
300 +}());
301 +
302 +/** Used to resolve the decompiled source of functions. */
303 +var funcToString = funcProto.toString;
304 +
305 +/** Used to check objects for own properties. */
306 +var hasOwnProperty = objectProto.hasOwnProperty;
307 +
308 +/**
309 + * Used to resolve the
310 + * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
311 + * of values.
312 + */
313 +var objectToString = objectProto.toString;
314 +
315 +/** Used to detect if a method is native. */
316 +var reIsNative = RegExp('^' +
317 + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
318 + .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
319 +);
320 +
321 +/** Built-in value references. */
322 +var Symbol = root.Symbol,
323 + getPrototype = overArg(Object.getPrototypeOf, Object),
324 + propertyIsEnumerable = objectProto.propertyIsEnumerable,
325 + splice = arrayProto.splice,
326 + spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined;
327 +
328 +/* Built-in method references for those with the same name as other `lodash` methods. */
329 +var nativeGetSymbols = Object.getOwnPropertySymbols,
330 + nativeMax = Math.max;
331 +
332 +/* Built-in method references that are verified to be native. */
333 +var Map = getNative(root, 'Map'),
334 + nativeCreate = getNative(Object, 'create');
335 +
336 +/**
337 + * Creates a hash object.
338 + *
339 + * @private
340 + * @constructor
341 + * @param {Array} [entries] The key-value pairs to cache.
342 + */
343 +function Hash(entries) {
344 + var index = -1,
345 + length = entries ? entries.length : 0;
346 +
347 + this.clear();
348 + while (++index < length) {
349 + var entry = entries[index];
350 + this.set(entry[0], entry[1]);
351 + }
352 +}
353 +
354 +/**
355 + * Removes all key-value entries from the hash.
356 + *
357 + * @private
358 + * @name clear
359 + * @memberOf Hash
360 + */
361 +function hashClear() {
362 + this.__data__ = nativeCreate ? nativeCreate(null) : {};
363 +}
364 +
365 +/**
366 + * Removes `key` and its value from the hash.
367 + *
368 + * @private
369 + * @name delete
370 + * @memberOf Hash
371 + * @param {Object} hash The hash to modify.
372 + * @param {string} key The key of the value to remove.
373 + * @returns {boolean} Returns `true` if the entry was removed, else `false`.
374 + */
375 +function hashDelete(key) {
376 + return this.has(key) && delete this.__data__[key];
377 +}
378 +
379 +/**
380 + * Gets the hash value for `key`.
381 + *
382 + * @private
383 + * @name get
384 + * @memberOf Hash
385 + * @param {string} key The key of the value to get.
386 + * @returns {*} Returns the entry value.
387 + */
388 +function hashGet(key) {
389 + var data = this.__data__;
390 + if (nativeCreate) {
391 + var result = data[key];
392 + return result === HASH_UNDEFINED ? undefined : result;
393 + }
394 + return hasOwnProperty.call(data, key) ? data[key] : undefined;
395 +}
396 +
397 +/**
398 + * Checks if a hash value for `key` exists.
399 + *
400 + * @private
401 + * @name has
402 + * @memberOf Hash
403 + * @param {string} key The key of the entry to check.
404 + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
405 + */
406 +function hashHas(key) {
407 + var data = this.__data__;
408 + return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
409 +}
410 +
411 +/**
412 + * Sets the hash `key` to `value`.
413 + *
414 + * @private
415 + * @name set
416 + * @memberOf Hash
417 + * @param {string} key The key of the value to set.
418 + * @param {*} value The value to set.
419 + * @returns {Object} Returns the hash instance.
420 + */
421 +function hashSet(key, value) {
422 + var data = this.__data__;
423 + data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
424 + return this;
425 +}
426 +
427 +// Add methods to `Hash`.
428 +Hash.prototype.clear = hashClear;
429 +Hash.prototype['delete'] = hashDelete;
430 +Hash.prototype.get = hashGet;
431 +Hash.prototype.has = hashHas;
432 +Hash.prototype.set = hashSet;
433 +
434 +/**
435 + * Creates an list cache object.
436 + *
437 + * @private
438 + * @constructor
439 + * @param {Array} [entries] The key-value pairs to cache.
440 + */
441 +function ListCache(entries) {
442 + var index = -1,
443 + length = entries ? entries.length : 0;
444 +
445 + this.clear();
446 + while (++index < length) {
447 + var entry = entries[index];
448 + this.set(entry[0], entry[1]);
449 + }
450 +}
451 +
452 +/**
453 + * Removes all key-value entries from the list cache.
454 + *
455 + * @private
456 + * @name clear
457 + * @memberOf ListCache
458 + */
459 +function listCacheClear() {
460 + this.__data__ = [];
461 +}
462 +
463 +/**
464 + * Removes `key` and its value from the list cache.
465 + *
466 + * @private
467 + * @name delete
468 + * @memberOf ListCache
469 + * @param {string} key The key of the value to remove.
470 + * @returns {boolean} Returns `true` if the entry was removed, else `false`.
471 + */
472 +function listCacheDelete(key) {
473 + var data = this.__data__,
474 + index = assocIndexOf(data, key);
475 +
476 + if (index < 0) {
477 + return false;
478 + }
479 + var lastIndex = data.length - 1;
480 + if (index == lastIndex) {
481 + data.pop();
482 + } else {
483 + splice.call(data, index, 1);
484 + }
485 + return true;
486 +}
487 +
488 +/**
489 + * Gets the list cache value for `key`.
490 + *
491 + * @private
492 + * @name get
493 + * @memberOf ListCache
494 + * @param {string} key The key of the value to get.
495 + * @returns {*} Returns the entry value.
496 + */
497 +function listCacheGet(key) {
498 + var data = this.__data__,
499 + index = assocIndexOf(data, key);
500 +
501 + return index < 0 ? undefined : data[index][1];
502 +}
503 +
504 +/**
505 + * Checks if a list cache value for `key` exists.
506 + *
507 + * @private
508 + * @name has
509 + * @memberOf ListCache
510 + * @param {string} key The key of the entry to check.
511 + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
512 + */
513 +function listCacheHas(key) {
514 + return assocIndexOf(this.__data__, key) > -1;
515 +}
516 +
517 +/**
518 + * Sets the list cache `key` to `value`.
519 + *
520 + * @private
521 + * @name set
522 + * @memberOf ListCache
523 + * @param {string} key The key of the value to set.
524 + * @param {*} value The value to set.
525 + * @returns {Object} Returns the list cache instance.
526 + */
527 +function listCacheSet(key, value) {
528 + var data = this.__data__,
529 + index = assocIndexOf(data, key);
530 +
531 + if (index < 0) {
532 + data.push([key, value]);
533 + } else {
534 + data[index][1] = value;
535 + }
536 + return this;
537 +}
538 +
539 +// Add methods to `ListCache`.
540 +ListCache.prototype.clear = listCacheClear;
541 +ListCache.prototype['delete'] = listCacheDelete;
542 +ListCache.prototype.get = listCacheGet;
543 +ListCache.prototype.has = listCacheHas;
544 +ListCache.prototype.set = listCacheSet;
545 +
546 +/**
547 + * Creates a map cache object to store key-value pairs.
548 + *
549 + * @private
550 + * @constructor
551 + * @param {Array} [entries] The key-value pairs to cache.
552 + */
553 +function MapCache(entries) {
554 + var index = -1,
555 + length = entries ? entries.length : 0;
556 +
557 + this.clear();
558 + while (++index < length) {
559 + var entry = entries[index];
560 + this.set(entry[0], entry[1]);
561 + }
562 +}
563 +
564 +/**
565 + * Removes all key-value entries from the map.
566 + *
567 + * @private
568 + * @name clear
569 + * @memberOf MapCache
570 + */
571 +function mapCacheClear() {
572 + this.__data__ = {
573 + 'hash': new Hash,
574 + 'map': new (Map || ListCache),
575 + 'string': new Hash
576 + };
577 +}
578 +
579 +/**
580 + * Removes `key` and its value from the map.
581 + *
582 + * @private
583 + * @name delete
584 + * @memberOf MapCache
585 + * @param {string} key The key of the value to remove.
586 + * @returns {boolean} Returns `true` if the entry was removed, else `false`.
587 + */
588 +function mapCacheDelete(key) {
589 + return getMapData(this, key)['delete'](key);
590 +}
591 +
592 +/**
593 + * Gets the map value for `key`.
594 + *
595 + * @private
596 + * @name get
597 + * @memberOf MapCache
598 + * @param {string} key The key of the value to get.
599 + * @returns {*} Returns the entry value.
600 + */
601 +function mapCacheGet(key) {
602 + return getMapData(this, key).get(key);
603 +}
604 +
605 +/**
606 + * Checks if a map value for `key` exists.
607 + *
608 + * @private
609 + * @name has
610 + * @memberOf MapCache
611 + * @param {string} key The key of the entry to check.
612 + * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
613 + */
614 +function mapCacheHas(key) {
615 + return getMapData(this, key).has(key);
616 +}
617 +
618 +/**
619 + * Sets the map `key` to `value`.
620 + *
621 + * @private
622 + * @name set
623 + * @memberOf MapCache
624 + * @param {string} key The key of the value to set.
625 + * @param {*} value The value to set.
626 + * @returns {Object} Returns the map cache instance.
627 + */
628 +function mapCacheSet(key, value) {
629 + getMapData(this, key).set(key, value);
630 + return this;
631 +}
632 +
633 +// Add methods to `MapCache`.
634 +MapCache.prototype.clear = mapCacheClear;
635 +MapCache.prototype['delete'] = mapCacheDelete;
636 +MapCache.prototype.get = mapCacheGet;
637 +MapCache.prototype.has = mapCacheHas;
638 +MapCache.prototype.set = mapCacheSet;
639 +
640 +/**
641 + *
642 + * Creates an array cache object to store unique values.
643 + *
644 + * @private
645 + * @constructor
646 + * @param {Array} [values] The values to cache.
647 + */
648 +function SetCache(values) {
649 + var index = -1,
650 + length = values ? values.length : 0;
651 +
652 + this.__data__ = new MapCache;
653 + while (++index < length) {
654 + this.add(values[index]);
655 + }
656 +}
657 +
658 +/**
659 + * Adds `value` to the array cache.
660 + *
661 + * @private
662 + * @name add
663 + * @memberOf SetCache
664 + * @alias push
665 + * @param {*} value The value to cache.
666 + * @returns {Object} Returns the cache instance.
667 + */
668 +function setCacheAdd(value) {
669 + this.__data__.set(value, HASH_UNDEFINED);
670 + return this;
671 +}
672 +
673 +/**
674 + * Checks if `value` is in the array cache.
675 + *
676 + * @private
677 + * @name has
678 + * @memberOf SetCache
679 + * @param {*} value The value to search for.
680 + * @returns {number} Returns `true` if `value` is found, else `false`.
681 + */
682 +function setCacheHas(value) {
683 + return this.__data__.has(value);
684 +}
685 +
686 +// Add methods to `SetCache`.
687 +SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
688 +SetCache.prototype.has = setCacheHas;
689 +
690 +/**
691 + * Creates an array of the enumerable property names of the array-like `value`.
692 + *
693 + * @private
694 + * @param {*} value The value to query.
695 + * @param {boolean} inherited Specify returning inherited property names.
696 + * @returns {Array} Returns the array of property names.
697 + */
698 +function arrayLikeKeys(value, inherited) {
699 + // Safari 8.1 makes `arguments.callee` enumerable in strict mode.
700 + // Safari 9 makes `arguments.length` enumerable in strict mode.
701 + var result = (isArray(value) || isArguments(value))
702 + ? baseTimes(value.length, String)
703 + : [];
704 +
705 + var length = result.length,
706 + skipIndexes = !!length;
707 +
708 + for (var key in value) {
709 + if ((inherited || hasOwnProperty.call(value, key)) &&
710 + !(skipIndexes && (key == 'length' || isIndex(key, length)))) {
711 + result.push(key);
712 + }
713 + }
714 + return result;
715 +}
716 +
717 +/**
718 + * Gets the index at which the `key` is found in `array` of key-value pairs.
719 + *
720 + * @private
721 + * @param {Array} array The array to inspect.
722 + * @param {*} key The key to search for.
723 + * @returns {number} Returns the index of the matched value, else `-1`.
724 + */
725 +function assocIndexOf(array, key) {
726 + var length = array.length;
727 + while (length--) {
728 + if (eq(array[length][0], key)) {
729 + return length;
730 + }
731 + }
732 + return -1;
733 +}
734 +
735 +/**
736 + * The base implementation of methods like `_.difference` without support
737 + * for excluding multiple arrays or iteratee shorthands.
738 + *
739 + * @private
740 + * @param {Array} array The array to inspect.
741 + * @param {Array} values The values to exclude.
742 + * @param {Function} [iteratee] The iteratee invoked per element.
743 + * @param {Function} [comparator] The comparator invoked per element.
744 + * @returns {Array} Returns the new array of filtered values.
745 + */
746 +function baseDifference(array, values, iteratee, comparator) {
747 + var index = -1,
748 + includes = arrayIncludes,
749 + isCommon = true,
750 + length = array.length,
751 + result = [],
752 + valuesLength = values.length;
753 +
754 + if (!length) {
755 + return result;
756 + }
757 + if (iteratee) {
758 + values = arrayMap(values, baseUnary(iteratee));
759 + }
760 + if (comparator) {
761 + includes = arrayIncludesWith;
762 + isCommon = false;
763 + }
764 + else if (values.length >= LARGE_ARRAY_SIZE) {
765 + includes = cacheHas;
766 + isCommon = false;
767 + values = new SetCache(values);
768 + }
769 + outer:
770 + while (++index < length) {
771 + var value = array[index],
772 + computed = iteratee ? iteratee(value) : value;
773 +
774 + value = (comparator || value !== 0) ? value : 0;
775 + if (isCommon && computed === computed) {
776 + var valuesIndex = valuesLength;
777 + while (valuesIndex--) {
778 + if (values[valuesIndex] === computed) {
779 + continue outer;
780 + }
781 + }
782 + result.push(value);
783 + }
784 + else if (!includes(values, computed, comparator)) {
785 + result.push(value);
786 + }
787 + }
788 + return result;
789 +}
790 +
791 +/**
792 + * The base implementation of `_.flatten` with support for restricting flattening.
793 + *
794 + * @private
795 + * @param {Array} array The array to flatten.
796 + * @param {number} depth The maximum recursion depth.
797 + * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
798 + * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
799 + * @param {Array} [result=[]] The initial result value.
800 + * @returns {Array} Returns the new flattened array.
801 + */
802 +function baseFlatten(array, depth, predicate, isStrict, result) {
803 + var index = -1,
804 + length = array.length;
805 +
806 + predicate || (predicate = isFlattenable);
807 + result || (result = []);
808 +
809 + while (++index < length) {
810 + var value = array[index];
811 + if (depth > 0 && predicate(value)) {
812 + if (depth > 1) {
813 + // Recursively flatten arrays (susceptible to call stack limits).
814 + baseFlatten(value, depth - 1, predicate, isStrict, result);
815 + } else {
816 + arrayPush(result, value);
817 + }
818 + } else if (!isStrict) {
819 + result[result.length] = value;
820 + }
821 + }
822 + return result;
823 +}
824 +
825 +/**
826 + * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
827 + * `keysFunc` and `symbolsFunc` to get the enumerable property names and
828 + * symbols of `object`.
829 + *
830 + * @private
831 + * @param {Object} object The object to query.
832 + * @param {Function} keysFunc The function to get the keys of `object`.
833 + * @param {Function} symbolsFunc The function to get the symbols of `object`.
834 + * @returns {Array} Returns the array of property names and symbols.
835 + */
836 +function baseGetAllKeys(object, keysFunc, symbolsFunc) {
837 + var result = keysFunc(object);
838 + return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
839 +}
840 +
841 +/**
842 + * The base implementation of `_.isNative` without bad shim checks.
843 + *
844 + * @private
845 + * @param {*} value The value to check.
846 + * @returns {boolean} Returns `true` if `value` is a native function,
847 + * else `false`.
848 + */
849 +function baseIsNative(value) {
850 + if (!isObject(value) || isMasked(value)) {
851 + return false;
852 + }
853 + var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor;
854 + return pattern.test(toSource(value));
855 +}
856 +
857 +/**
858 + * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
859 + *
860 + * @private
861 + * @param {Object} object The object to query.
862 + * @returns {Array} Returns the array of property names.
863 + */
864 +function baseKeysIn(object) {
865 + if (!isObject(object)) {
866 + return nativeKeysIn(object);
867 + }
868 + var isProto = isPrototype(object),
869 + result = [];
870 +
871 + for (var key in object) {
872 + if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
873 + result.push(key);
874 + }
875 + }
876 + return result;
877 +}
878 +
879 +/**
880 + * The base implementation of `_.pick` without support for individual
881 + * property identifiers.
882 + *
883 + * @private
884 + * @param {Object} object The source object.
885 + * @param {string[]} props The property identifiers to pick.
886 + * @returns {Object} Returns the new object.
887 + */
888 +function basePick(object, props) {
889 + object = Object(object);
890 + return basePickBy(object, props, function(value, key) {
891 + return key in object;
892 + });
893 +}
894 +
895 +/**
896 + * The base implementation of `_.pickBy` without support for iteratee shorthands.
897 + *
898 + * @private
899 + * @param {Object} object The source object.
900 + * @param {string[]} props The property identifiers to pick from.
901 + * @param {Function} predicate The function invoked per property.
902 + * @returns {Object} Returns the new object.
903 + */
904 +function basePickBy(object, props, predicate) {
905 + var index = -1,
906 + length = props.length,
907 + result = {};
908 +
909 + while (++index < length) {
910 + var key = props[index],
911 + value = object[key];
912 +
913 + if (predicate(value, key)) {
914 + result[key] = value;
915 + }
916 + }
917 + return result;
918 +}
919 +
920 +/**
921 + * The base implementation of `_.rest` which doesn't validate or coerce arguments.
922 + *
923 + * @private
924 + * @param {Function} func The function to apply a rest parameter to.
925 + * @param {number} [start=func.length-1] The start position of the rest parameter.
926 + * @returns {Function} Returns the new function.
927 + */
928 +function baseRest(func, start) {
929 + start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
930 + return function() {
931 + var args = arguments,
932 + index = -1,
933 + length = nativeMax(args.length - start, 0),
934 + array = Array(length);
935 +
936 + while (++index < length) {
937 + array[index] = args[start + index];
938 + }
939 + index = -1;
940 + var otherArgs = Array(start + 1);
941 + while (++index < start) {
942 + otherArgs[index] = args[index];
943 + }
944 + otherArgs[start] = array;
945 + return apply(func, this, otherArgs);
946 + };
947 +}
948 +
949 +/**
950 + * Creates an array of own and inherited enumerable property names and
951 + * symbols of `object`.
952 + *
953 + * @private
954 + * @param {Object} object The object to query.
955 + * @returns {Array} Returns the array of property names and symbols.
956 + */
957 +function getAllKeysIn(object) {
958 + return baseGetAllKeys(object, keysIn, getSymbolsIn);
959 +}
960 +
961 +/**
962 + * Gets the data for `map`.
963 + *
964 + * @private
965 + * @param {Object} map The map to query.
966 + * @param {string} key The reference key.
967 + * @returns {*} Returns the map data.
968 + */
969 +function getMapData(map, key) {
970 + var data = map.__data__;
971 + return isKeyable(key)
972 + ? data[typeof key == 'string' ? 'string' : 'hash']
973 + : data.map;
974 +}
975 +
976 +/**
977 + * Gets the native function at `key` of `object`.
978 + *
979 + * @private
980 + * @param {Object} object The object to query.
981 + * @param {string} key The key of the method to get.
982 + * @returns {*} Returns the function if it's native, else `undefined`.
983 + */
984 +function getNative(object, key) {
985 + var value = getValue(object, key);
986 + return baseIsNative(value) ? value : undefined;
987 +}
988 +
989 +/**
990 + * Creates an array of the own enumerable symbol properties of `object`.
991 + *
992 + * @private
993 + * @param {Object} object The object to query.
994 + * @returns {Array} Returns the array of symbols.
995 + */
996 +var getSymbols = nativeGetSymbols ? overArg(nativeGetSymbols, Object) : stubArray;
997 +
998 +/**
999 + * Creates an array of the own and inherited enumerable symbol properties
1000 + * of `object`.
1001 + *
1002 + * @private
1003 + * @param {Object} object The object to query.
1004 + * @returns {Array} Returns the array of symbols.
1005 + */
1006 +var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) {
1007 + var result = [];
1008 + while (object) {
1009 + arrayPush(result, getSymbols(object));
1010 + object = getPrototype(object);
1011 + }
1012 + return result;
1013 +};
1014 +
1015 +/**
1016 + * Checks if `value` is a flattenable `arguments` object or array.
1017 + *
1018 + * @private
1019 + * @param {*} value The value to check.
1020 + * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
1021 + */
1022 +function isFlattenable(value) {
1023 + return isArray(value) || isArguments(value) ||
1024 + !!(spreadableSymbol && value && value[spreadableSymbol]);
1025 +}
1026 +
1027 +/**
1028 + * Checks if `value` is a valid array-like index.
1029 + *
1030 + * @private
1031 + * @param {*} value The value to check.
1032 + * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
1033 + * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
1034 + */
1035 +function isIndex(value, length) {
1036 + length = length == null ? MAX_SAFE_INTEGER : length;
1037 + return !!length &&
1038 + (typeof value == 'number' || reIsUint.test(value)) &&
1039 + (value > -1 && value % 1 == 0 && value < length);
1040 +}
1041 +
1042 +/**
1043 + * Checks if `value` is suitable for use as unique object key.
1044 + *
1045 + * @private
1046 + * @param {*} value The value to check.
1047 + * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
1048 + */
1049 +function isKeyable(value) {
1050 + var type = typeof value;
1051 + return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
1052 + ? (value !== '__proto__')
1053 + : (value === null);
1054 +}
1055 +
1056 +/**
1057 + * Checks if `func` has its source masked.
1058 + *
1059 + * @private
1060 + * @param {Function} func The function to check.
1061 + * @returns {boolean} Returns `true` if `func` is masked, else `false`.
1062 + */
1063 +function isMasked(func) {
1064 + return !!maskSrcKey && (maskSrcKey in func);
1065 +}
1066 +
1067 +/**
1068 + * Checks if `value` is likely a prototype object.
1069 + *
1070 + * @private
1071 + * @param {*} value The value to check.
1072 + * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
1073 + */
1074 +function isPrototype(value) {
1075 + var Ctor = value && value.constructor,
1076 + proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
1077 +
1078 + return value === proto;
1079 +}
1080 +
1081 +/**
1082 + * This function is like
1083 + * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
1084 + * except that it includes inherited enumerable properties.
1085 + *
1086 + * @private
1087 + * @param {Object} object The object to query.
1088 + * @returns {Array} Returns the array of property names.
1089 + */
1090 +function nativeKeysIn(object) {
1091 + var result = [];
1092 + if (object != null) {
1093 + for (var key in Object(object)) {
1094 + result.push(key);
1095 + }
1096 + }
1097 + return result;
1098 +}
1099 +
1100 +/**
1101 + * Converts `value` to a string key if it's not a string or symbol.
1102 + *
1103 + * @private
1104 + * @param {*} value The value to inspect.
1105 + * @returns {string|symbol} Returns the key.
1106 + */
1107 +function toKey(value) {
1108 + if (typeof value == 'string' || isSymbol(value)) {
1109 + return value;
1110 + }
1111 + var result = (value + '');
1112 + return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
1113 +}
1114 +
1115 +/**
1116 + * Converts `func` to its source code.
1117 + *
1118 + * @private
1119 + * @param {Function} func The function to process.
1120 + * @returns {string} Returns the source code.
1121 + */
1122 +function toSource(func) {
1123 + if (func != null) {
1124 + try {
1125 + return funcToString.call(func);
1126 + } catch (e) {}
1127 + try {
1128 + return (func + '');
1129 + } catch (e) {}
1130 + }
1131 + return '';
1132 +}
1133 +
1134 +/**
1135 + * Performs a
1136 + * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
1137 + * comparison between two values to determine if they are equivalent.
1138 + *
1139 + * @static
1140 + * @memberOf _
1141 + * @since 4.0.0
1142 + * @category Lang
1143 + * @param {*} value The value to compare.
1144 + * @param {*} other The other value to compare.
1145 + * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
1146 + * @example
1147 + *
1148 + * var object = { 'a': 1 };
1149 + * var other = { 'a': 1 };
1150 + *
1151 + * _.eq(object, object);
1152 + * // => true
1153 + *
1154 + * _.eq(object, other);
1155 + * // => false
1156 + *
1157 + * _.eq('a', 'a');
1158 + * // => true
1159 + *
1160 + * _.eq('a', Object('a'));
1161 + * // => false
1162 + *
1163 + * _.eq(NaN, NaN);
1164 + * // => true
1165 + */
1166 +function eq(value, other) {
1167 + return value === other || (value !== value && other !== other);
1168 +}
1169 +
1170 +/**
1171 + * Checks if `value` is likely an `arguments` object.
1172 + *
1173 + * @static
1174 + * @memberOf _
1175 + * @since 0.1.0
1176 + * @category Lang
1177 + * @param {*} value The value to check.
1178 + * @returns {boolean} Returns `true` if `value` is an `arguments` object,
1179 + * else `false`.
1180 + * @example
1181 + *
1182 + * _.isArguments(function() { return arguments; }());
1183 + * // => true
1184 + *
1185 + * _.isArguments([1, 2, 3]);
1186 + * // => false
1187 + */
1188 +function isArguments(value) {
1189 + // Safari 8.1 makes `arguments.callee` enumerable in strict mode.
1190 + return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
1191 + (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
1192 +}
1193 +
1194 +/**
1195 + * Checks if `value` is classified as an `Array` object.
1196 + *
1197 + * @static
1198 + * @memberOf _
1199 + * @since 0.1.0
1200 + * @category Lang
1201 + * @param {*} value The value to check.
1202 + * @returns {boolean} Returns `true` if `value` is an array, else `false`.
1203 + * @example
1204 + *
1205 + * _.isArray([1, 2, 3]);
1206 + * // => true
1207 + *
1208 + * _.isArray(document.body.children);
1209 + * // => false
1210 + *
1211 + * _.isArray('abc');
1212 + * // => false
1213 + *
1214 + * _.isArray(_.noop);
1215 + * // => false
1216 + */
1217 +var isArray = Array.isArray;
1218 +
1219 +/**
1220 + * Checks if `value` is array-like. A value is considered array-like if it's
1221 + * not a function and has a `value.length` that's an integer greater than or
1222 + * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
1223 + *
1224 + * @static
1225 + * @memberOf _
1226 + * @since 4.0.0
1227 + * @category Lang
1228 + * @param {*} value The value to check.
1229 + * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
1230 + * @example
1231 + *
1232 + * _.isArrayLike([1, 2, 3]);
1233 + * // => true
1234 + *
1235 + * _.isArrayLike(document.body.children);
1236 + * // => true
1237 + *
1238 + * _.isArrayLike('abc');
1239 + * // => true
1240 + *
1241 + * _.isArrayLike(_.noop);
1242 + * // => false
1243 + */
1244 +function isArrayLike(value) {
1245 + return value != null && isLength(value.length) && !isFunction(value);
1246 +}
1247 +
1248 +/**
1249 + * This method is like `_.isArrayLike` except that it also checks if `value`
1250 + * is an object.
1251 + *
1252 + * @static
1253 + * @memberOf _
1254 + * @since 4.0.0
1255 + * @category Lang
1256 + * @param {*} value The value to check.
1257 + * @returns {boolean} Returns `true` if `value` is an array-like object,
1258 + * else `false`.
1259 + * @example
1260 + *
1261 + * _.isArrayLikeObject([1, 2, 3]);
1262 + * // => true
1263 + *
1264 + * _.isArrayLikeObject(document.body.children);
1265 + * // => true
1266 + *
1267 + * _.isArrayLikeObject('abc');
1268 + * // => false
1269 + *
1270 + * _.isArrayLikeObject(_.noop);
1271 + * // => false
1272 + */
1273 +function isArrayLikeObject(value) {
1274 + return isObjectLike(value) && isArrayLike(value);
1275 +}
1276 +
1277 +/**
1278 + * Checks if `value` is classified as a `Function` object.
1279 + *
1280 + * @static
1281 + * @memberOf _
1282 + * @since 0.1.0
1283 + * @category Lang
1284 + * @param {*} value The value to check.
1285 + * @returns {boolean} Returns `true` if `value` is a function, else `false`.
1286 + * @example
1287 + *
1288 + * _.isFunction(_);
1289 + * // => true
1290 + *
1291 + * _.isFunction(/abc/);
1292 + * // => false
1293 + */
1294 +function isFunction(value) {
1295 + // The use of `Object#toString` avoids issues with the `typeof` operator
1296 + // in Safari 8-9 which returns 'object' for typed array and other constructors.
1297 + var tag = isObject(value) ? objectToString.call(value) : '';
1298 + return tag == funcTag || tag == genTag;
1299 +}
1300 +
1301 +/**
1302 + * Checks if `value` is a valid array-like length.
1303 + *
1304 + * **Note:** This method is loosely based on
1305 + * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
1306 + *
1307 + * @static
1308 + * @memberOf _
1309 + * @since 4.0.0
1310 + * @category Lang
1311 + * @param {*} value The value to check.
1312 + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
1313 + * @example
1314 + *
1315 + * _.isLength(3);
1316 + * // => true
1317 + *
1318 + * _.isLength(Number.MIN_VALUE);
1319 + * // => false
1320 + *
1321 + * _.isLength(Infinity);
1322 + * // => false
1323 + *
1324 + * _.isLength('3');
1325 + * // => false
1326 + */
1327 +function isLength(value) {
1328 + return typeof value == 'number' &&
1329 + value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
1330 +}
1331 +
1332 +/**
1333 + * Checks if `value` is the
1334 + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
1335 + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
1336 + *
1337 + * @static
1338 + * @memberOf _
1339 + * @since 0.1.0
1340 + * @category Lang
1341 + * @param {*} value The value to check.
1342 + * @returns {boolean} Returns `true` if `value` is an object, else `false`.
1343 + * @example
1344 + *
1345 + * _.isObject({});
1346 + * // => true
1347 + *
1348 + * _.isObject([1, 2, 3]);
1349 + * // => true
1350 + *
1351 + * _.isObject(_.noop);
1352 + * // => true
1353 + *
1354 + * _.isObject(null);
1355 + * // => false
1356 + */
1357 +function isObject(value) {
1358 + var type = typeof value;
1359 + return !!value && (type == 'object' || type == 'function');
1360 +}
1361 +
1362 +/**
1363 + * Checks if `value` is object-like. A value is object-like if it's not `null`
1364 + * and has a `typeof` result of "object".
1365 + *
1366 + * @static
1367 + * @memberOf _
1368 + * @since 4.0.0
1369 + * @category Lang
1370 + * @param {*} value The value to check.
1371 + * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
1372 + * @example
1373 + *
1374 + * _.isObjectLike({});
1375 + * // => true
1376 + *
1377 + * _.isObjectLike([1, 2, 3]);
1378 + * // => true
1379 + *
1380 + * _.isObjectLike(_.noop);
1381 + * // => false
1382 + *
1383 + * _.isObjectLike(null);
1384 + * // => false
1385 + */
1386 +function isObjectLike(value) {
1387 + return !!value && typeof value == 'object';
1388 +}
1389 +
1390 +/**
1391 + * Checks if `value` is classified as a `Symbol` primitive or object.
1392 + *
1393 + * @static
1394 + * @memberOf _
1395 + * @since 4.0.0
1396 + * @category Lang
1397 + * @param {*} value The value to check.
1398 + * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
1399 + * @example
1400 + *
1401 + * _.isSymbol(Symbol.iterator);
1402 + * // => true
1403 + *
1404 + * _.isSymbol('abc');
1405 + * // => false
1406 + */
1407 +function isSymbol(value) {
1408 + return typeof value == 'symbol' ||
1409 + (isObjectLike(value) && objectToString.call(value) == symbolTag);
1410 +}
1411 +
1412 +/**
1413 + * Creates an array of the own and inherited enumerable property names of `object`.
1414 + *
1415 + * **Note:** Non-object values are coerced to objects.
1416 + *
1417 + * @static
1418 + * @memberOf _
1419 + * @since 3.0.0
1420 + * @category Object
1421 + * @param {Object} object The object to query.
1422 + * @returns {Array} Returns the array of property names.
1423 + * @example
1424 + *
1425 + * function Foo() {
1426 + * this.a = 1;
1427 + * this.b = 2;
1428 + * }
1429 + *
1430 + * Foo.prototype.c = 3;
1431 + *
1432 + * _.keysIn(new Foo);
1433 + * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
1434 + */
1435 +function keysIn(object) {
1436 + return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object);
1437 +}
1438 +
1439 +/**
1440 + * The opposite of `_.pick`; this method creates an object composed of the
1441 + * own and inherited enumerable string keyed properties of `object` that are
1442 + * not omitted.
1443 + *
1444 + * @static
1445 + * @since 0.1.0
1446 + * @memberOf _
1447 + * @category Object
1448 + * @param {Object} object The source object.
1449 + * @param {...(string|string[])} [props] The property identifiers to omit.
1450 + * @returns {Object} Returns the new object.
1451 + * @example
1452 + *
1453 + * var object = { 'a': 1, 'b': '2', 'c': 3 };
1454 + *
1455 + * _.omit(object, ['a', 'c']);
1456 + * // => { 'b': '2' }
1457 + */
1458 +var omit = baseRest(function(object, props) {
1459 + if (object == null) {
1460 + return {};
1461 + }
1462 + props = arrayMap(baseFlatten(props, 1), toKey);
1463 + return basePick(object, baseDifference(getAllKeysIn(object), props));
1464 +});
1465 +
1466 +/**
1467 + * This method returns a new empty array.
1468 + *
1469 + * @static
1470 + * @memberOf _
1471 + * @since 4.13.0
1472 + * @category Util
1473 + * @returns {Array} Returns the new empty array.
1474 + * @example
1475 + *
1476 + * var arrays = _.times(2, _.stubArray);
1477 + *
1478 + * console.log(arrays);
1479 + * // => [[], []]
1480 + *
1481 + * console.log(arrays[0] === arrays[1]);
1482 + * // => false
1483 + */
1484 +function stubArray() {
1485 + return [];
1486 +}
1487 +
1488 +module.exports = omit;
1 +{
2 + "_from": "lodash.omit@^4.5.0",
3 + "_id": "lodash.omit@4.5.0",
4 + "_inBundle": false,
5 + "_integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=",
6 + "_location": "/lodash.omit",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "lodash.omit@^4.5.0",
12 + "name": "lodash.omit",
13 + "escapedName": "lodash.omit",
14 + "rawSpec": "^4.5.0",
15 + "saveSpec": null,
16 + "fetchSpec": "^4.5.0"
17 + },
18 + "_requiredBy": [
19 + "/messaging-api-line"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
22 + "_shasum": "6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60",
23 + "_spec": "lodash.omit@^4.5.0",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\messaging-api-line",
25 + "author": {
26 + "name": "John-David Dalton",
27 + "email": "john.david.dalton@gmail.com",
28 + "url": "http://allyoucanleet.com/"
29 + },
30 + "bugs": {
31 + "url": "https://github.com/lodash/lodash/issues"
32 + },
33 + "bundleDependencies": false,
34 + "contributors": [
35 + {
36 + "name": "John-David Dalton",
37 + "email": "john.david.dalton@gmail.com",
38 + "url": "http://allyoucanleet.com/"
39 + },
40 + {
41 + "name": "Blaine Bublitz",
42 + "email": "blaine.bublitz@gmail.com",
43 + "url": "https://github.com/phated"
44 + },
45 + {
46 + "name": "Mathias Bynens",
47 + "email": "mathias@qiwi.be",
48 + "url": "https://mathiasbynens.be/"
49 + }
50 + ],
51 + "deprecated": false,
52 + "description": "The lodash method `_.omit` exported as a module.",
53 + "homepage": "https://lodash.com/",
54 + "icon": "https://lodash.com/icon.svg",
55 + "keywords": [
56 + "lodash-modularized",
57 + "omit"
58 + ],
59 + "license": "MIT",
60 + "name": "lodash.omit",
61 + "repository": {
62 + "type": "git",
63 + "url": "git+https://github.com/lodash/lodash.git"
64 + },
65 + "scripts": {
66 + "test": "echo \"See https://travis-ci.org/lodash/lodash-cli for testing details.\""
67 + },
68 + "version": "4.5.0"
69 +}
1 +The MIT License (MIT)
2 +
3 +Copyright (c) 2015 Andres Suarez <zertosh@gmail.com>
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
13 +all 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
21 +THE SOFTWARE.
1 +# loose-envify
2 +
3 +[![Build Status](https://travis-ci.org/zertosh/loose-envify.svg?branch=master)](https://travis-ci.org/zertosh/loose-envify)
4 +
5 +Fast (and loose) selective `process.env` replacer using [js-tokens](https://github.com/lydell/js-tokens) instead of an AST. Works just like [envify](https://github.com/hughsk/envify) but much faster.
6 +
7 +## Gotchas
8 +
9 +* Doesn't handle broken syntax.
10 +* Doesn't look inside embedded expressions in template strings.
11 + - **this won't work:**
12 + ```js
13 + console.log(`the current env is ${process.env.NODE_ENV}`);
14 + ```
15 +* Doesn't replace oddly-spaced or oddly-commented expressions.
16 + - **this won't work:**
17 + ```js
18 + console.log(process./*won't*/env./*work*/NODE_ENV);
19 + ```
20 +
21 +## Usage/Options
22 +
23 +loose-envify has the exact same interface as [envify](https://github.com/hughsk/envify), including the CLI.
24 +
25 +## Benchmark
26 +
27 +```
28 +envify:
29 +
30 + $ for i in {1..5}; do node bench/bench.js 'envify'; done
31 + 708ms
32 + 727ms
33 + 791ms
34 + 719ms
35 + 720ms
36 +
37 +loose-envify:
38 +
39 + $ for i in {1..5}; do node bench/bench.js '../'; done
40 + 51ms
41 + 52ms
42 + 52ms
43 + 52ms
44 + 52ms
45 +```
1 +#!/usr/bin/env node
2 +'use strict';
3 +
4 +var looseEnvify = require('./');
5 +var fs = require('fs');
6 +
7 +if (process.argv[2]) {
8 + fs.createReadStream(process.argv[2], {encoding: 'utf8'})
9 + .pipe(looseEnvify(process.argv[2]))
10 + .pipe(process.stdout);
11 +} else {
12 + process.stdin.resume()
13 + process.stdin
14 + .pipe(looseEnvify(__filename))
15 + .pipe(process.stdout);
16 +}
1 +// envify compatibility
2 +'use strict';
3 +
4 +module.exports = require('./loose-envify');
1 +'use strict';
2 +
3 +module.exports = require('./loose-envify')(process.env);
1 +'use strict';
2 +
3 +var stream = require('stream');
4 +var util = require('util');
5 +var replace = require('./replace');
6 +
7 +var jsonExtRe = /\.json$/;
8 +
9 +module.exports = function(rootEnv) {
10 + rootEnv = rootEnv || process.env;
11 + return function (file, trOpts) {
12 + if (jsonExtRe.test(file)) {
13 + return stream.PassThrough();
14 + }
15 + var envs = trOpts ? [rootEnv, trOpts] : [rootEnv];
16 + return new LooseEnvify(envs);
17 + };
18 +};
19 +
20 +function LooseEnvify(envs) {
21 + stream.Transform.call(this);
22 + this._data = '';
23 + this._envs = envs;
24 +}
25 +util.inherits(LooseEnvify, stream.Transform);
26 +
27 +LooseEnvify.prototype._transform = function(buf, enc, cb) {
28 + this._data += buf;
29 + cb();
30 +};
31 +
32 +LooseEnvify.prototype._flush = function(cb) {
33 + var replaced = replace(this._data, this._envs);
34 + this.push(replaced);
35 + cb();
36 +};
1 +{
2 + "_from": "loose-envify@^1.0.0",
3 + "_id": "loose-envify@1.4.0",
4 + "_inBundle": false,
5 + "_integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
6 + "_location": "/loose-envify",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "loose-envify@^1.0.0",
12 + "name": "loose-envify",
13 + "escapedName": "loose-envify",
14 + "rawSpec": "^1.0.0",
15 + "saveSpec": null,
16 + "fetchSpec": "^1.0.0"
17 + },
18 + "_requiredBy": [
19 + "/invariant"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
22 + "_shasum": "71ee51fa7be4caec1a63839f7e682d8132d30caf",
23 + "_spec": "loose-envify@^1.0.0",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\invariant",
25 + "author": {
26 + "name": "Andres Suarez",
27 + "email": "zertosh@gmail.com"
28 + },
29 + "bin": {
30 + "loose-envify": "cli.js"
31 + },
32 + "bugs": {
33 + "url": "https://github.com/zertosh/loose-envify/issues"
34 + },
35 + "bundleDependencies": false,
36 + "dependencies": {
37 + "js-tokens": "^3.0.0 || ^4.0.0"
38 + },
39 + "deprecated": false,
40 + "description": "Fast (and loose) selective `process.env` replacer using js-tokens instead of an AST",
41 + "devDependencies": {
42 + "browserify": "^13.1.1",
43 + "envify": "^3.4.0",
44 + "tap": "^8.0.0"
45 + },
46 + "homepage": "https://github.com/zertosh/loose-envify",
47 + "keywords": [
48 + "environment",
49 + "variables",
50 + "browserify",
51 + "browserify-transform",
52 + "transform",
53 + "source",
54 + "configuration"
55 + ],
56 + "license": "MIT",
57 + "main": "index.js",
58 + "name": "loose-envify",
59 + "repository": {
60 + "type": "git",
61 + "url": "git://github.com/zertosh/loose-envify.git"
62 + },
63 + "scripts": {
64 + "test": "tap test/*.js"
65 + },
66 + "version": "1.4.0"
67 +}
1 +'use strict';
2 +
3 +var jsTokens = require('js-tokens').default;
4 +
5 +var processEnvRe = /\bprocess\.env\.[_$a-zA-Z][$\w]+\b/;
6 +var spaceOrCommentRe = /^(?:\s|\/[/*])/;
7 +
8 +function replace(src, envs) {
9 + if (!processEnvRe.test(src)) {
10 + return src;
11 + }
12 +
13 + var out = [];
14 + var purge = envs.some(function(env) {
15 + return env._ && env._.indexOf('purge') !== -1;
16 + });
17 +
18 + jsTokens.lastIndex = 0
19 + var parts = src.match(jsTokens);
20 +
21 + for (var i = 0; i < parts.length; i++) {
22 + if (parts[i ] === 'process' &&
23 + parts[i + 1] === '.' &&
24 + parts[i + 2] === 'env' &&
25 + parts[i + 3] === '.') {
26 + var prevCodeToken = getAdjacentCodeToken(-1, parts, i);
27 + var nextCodeToken = getAdjacentCodeToken(1, parts, i + 4);
28 + var replacement = getReplacementString(envs, parts[i + 4], purge);
29 + if (prevCodeToken !== '.' &&
30 + nextCodeToken !== '.' &&
31 + nextCodeToken !== '=' &&
32 + typeof replacement === 'string') {
33 + out.push(replacement);
34 + i += 4;
35 + continue;
36 + }
37 + }
38 + out.push(parts[i]);
39 + }
40 +
41 + return out.join('');
42 +}
43 +
44 +function getAdjacentCodeToken(dir, parts, i) {
45 + while (true) {
46 + var part = parts[i += dir];
47 + if (!spaceOrCommentRe.test(part)) {
48 + return part;
49 + }
50 + }
51 +}
52 +
53 +function getReplacementString(envs, name, purge) {
54 + for (var j = 0; j < envs.length; j++) {
55 + var env = envs[j];
56 + if (typeof env[name] !== 'undefined') {
57 + return JSON.stringify(env[name]);
58 + }
59 + }
60 + if (purge) {
61 + return 'undefined';
62 + }
63 +}
64 +
65 +module.exports = replace;
1 +The MIT License (MIT)
2 +
3 +Copyright (c) 2017-present Yoctol (github.com/Yoctol/messaging-apis)
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
13 +all 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
21 +THE SOFTWARE.
This diff could not be displayed because it is too large.
1 +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.default = void 0;
2 +var _invariant = _interopRequireDefault(require("invariant"));
3 +var _lodash = _interopRequireDefault(require("lodash.omit"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function ownKeys(object, enumerableOnly) {var keys = Object.keys(object);if (Object.getOwnPropertySymbols) {var symbols = Object.getOwnPropertySymbols(object);if (enumerableOnly) symbols = symbols.filter(function (sym) {return Object.getOwnPropertyDescriptor(object, sym).enumerable;});keys.push.apply(keys, symbols);}return keys;}function _objectSpread(target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i] != null ? arguments[i] : {};if (i % 2) {ownKeys(source, true).forEach(function (key) {_defineProperty(target, key, source[key]);});} else if (Object.getOwnPropertyDescriptors) {Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));} else {ownKeys(source).forEach(function (key) {Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));});}}return target;}function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;}
4 +
5 +
6 +
7 +
8 +
9 +
10 +
11 +
12 +
13 +
14 +
15 +
16 +
17 +
18 +
19 +
20 +
21 +
22 +
23 +
24 +
25 +
26 +
27 +
28 +
29 +
30 +
31 +function createText(text, options = {}) {
32 + return _objectSpread({
33 + type: 'text',
34 + text },
35 + (0, _lodash.default)(options, 'accessToken'));
36 +
37 +}
38 +
39 +function createImage(
40 +contentUrlOrImage,
41 +previewUrlOrOptions)
42 +{
43 + if (previewUrlOrOptions) {
44 + if (
45 + typeof contentUrlOrImage === 'object' &&
46 + typeof previewUrlOrOptions === 'object')
47 + {
48 + const image = contentUrlOrImage;
49 + const options = previewUrlOrOptions;
50 + return _objectSpread({
51 + type: 'image',
52 + originalContentUrl: image.originalContentUrl,
53 + previewImageUrl: image.previewImageUrl || image.originalContentUrl },
54 + (0, _lodash.default)(options, 'accessToken'));
55 +
56 + }
57 +
58 + if (
59 + typeof contentUrlOrImage === 'string' &&
60 + typeof previewUrlOrOptions === 'string')
61 + {
62 + return {
63 + type: 'image',
64 + originalContentUrl: contentUrlOrImage,
65 + previewImageUrl: previewUrlOrOptions };
66 +
67 + }
68 + } else {
69 + if (typeof contentUrlOrImage === 'object') {
70 + const image = contentUrlOrImage;
71 + return {
72 + type: 'image',
73 + originalContentUrl: image.originalContentUrl,
74 + previewImageUrl: image.previewImageUrl || image.originalContentUrl };
75 +
76 + }
77 +
78 + if (typeof contentUrlOrImage === 'string') {
79 + return {
80 + type: 'image',
81 + originalContentUrl: contentUrlOrImage,
82 + previewImageUrl: contentUrlOrImage };
83 +
84 + }
85 + }
86 +
87 + (0, _invariant.default)(false, 'Line#createImage: Wrong type of arguments.');
88 +}
89 +
90 +function createVideo(
91 +contentUrlOrVideo,
92 +previewImageUrlOrOptions)
93 +{
94 + if (
95 + typeof contentUrlOrVideo === 'string' &&
96 + typeof previewImageUrlOrOptions === 'string')
97 + {
98 + return {
99 + type: 'video',
100 + originalContentUrl: contentUrlOrVideo,
101 + previewImageUrl: previewImageUrlOrOptions };
102 +
103 + }
104 +
105 + if (
106 + typeof contentUrlOrVideo === 'object' && (
107 + !previewImageUrlOrOptions || typeof previewImageUrlOrOptions === 'object'))
108 + {
109 + const video = contentUrlOrVideo;
110 + const options = previewImageUrlOrOptions || {};
111 + return _objectSpread({
112 + type: 'video',
113 + originalContentUrl: video.originalContentUrl,
114 + previewImageUrl: video.previewImageUrl },
115 + (0, _lodash.default)(options, 'accessToken'));
116 +
117 + }
118 +
119 + (0, _invariant.default)(false, 'Line#createVideo: Wrong type of arguments.');
120 +}
121 +
122 +function createAudio(
123 +contentUrlOrAudio,
124 +durationOrOptions)
125 +{
126 + if (
127 + typeof contentUrlOrAudio === 'string' &&
128 + typeof durationOrOptions === 'number')
129 + {
130 + return {
131 + type: 'audio',
132 + originalContentUrl: contentUrlOrAudio,
133 + duration: durationOrOptions };
134 +
135 + }
136 +
137 + if (
138 + typeof contentUrlOrAudio === 'object' && (
139 + !durationOrOptions || typeof durationOrOptions === 'object'))
140 + {
141 + const audio = contentUrlOrAudio;
142 + const options = durationOrOptions || {};
143 + return _objectSpread({
144 + type: 'audio',
145 + originalContentUrl: audio.originalContentUrl,
146 + duration: audio.duration },
147 + (0, _lodash.default)(options, 'accessToken'));
148 +
149 + }
150 +
151 + (0, _invariant.default)(false, 'Line#createAudio: Wrong type of arguments.');
152 +}
153 +
154 +function createLocation(
155 +{ title, address, latitude, longitude },
156 +options = {})
157 +{
158 + return _objectSpread({
159 + type: 'location',
160 + title,
161 + address,
162 + latitude,
163 + longitude },
164 + (0, _lodash.default)(options, 'accessToken'));
165 +
166 +}
167 +
168 +function createSticker(
169 +packageIdOrSticker,
170 +stickerIdOrOptions)
171 +{
172 + if (
173 + typeof packageIdOrSticker === 'string' &&
174 + typeof stickerIdOrOptions === 'string')
175 + {
176 + return {
177 + type: 'sticker',
178 + packageId: packageIdOrSticker,
179 + stickerId: stickerIdOrOptions };
180 +
181 + }
182 +
183 + if (
184 + typeof packageIdOrSticker === 'object' && (
185 + !stickerIdOrOptions || typeof stickerIdOrOptions === 'object'))
186 + {
187 + const sticker = packageIdOrSticker;
188 + const options = stickerIdOrOptions || {};
189 + return _objectSpread({
190 + type: 'sticker',
191 + packageId: sticker.packageId,
192 + stickerId: sticker.stickerId },
193 + (0, _lodash.default)(options, 'accessToken'));
194 +
195 + }
196 +
197 + (0, _invariant.default)(false, 'Line#createSticker: Wrong type of arguments.');
198 +}
199 +
200 +function createImagemap(
201 +altText,
202 +{
203 + baseUrl,
204 + baseSize,
205 + baseHeight,
206 + baseWidth,
207 + video,
208 + actions },
209 +
210 +
211 +
212 +
213 +
214 +
215 +
216 +
217 +
218 +
219 +
220 +options = {})
221 +{
222 + return _objectSpread({
223 + type: 'imagemap',
224 + baseUrl,
225 + altText,
226 + baseSize: baseSize || {
227 + height: baseHeight,
228 + width: baseWidth },
229 +
230 + video,
231 + actions },
232 + (0, _lodash.default)(options, 'accessToken'));
233 +
234 +}
235 +
236 +function createTemplate(
237 +altText,
238 +template,
239 +options = {})
240 +{
241 + return _objectSpread({
242 + type: 'template',
243 + altText,
244 + template },
245 + (0, _lodash.default)(options, 'accessToken'));
246 +
247 +}
248 +
249 +function createButtonTemplate(
250 +altText,
251 +{
252 + thumbnailImageUrl,
253 + imageAspectRatio,
254 + imageSize,
255 + imageBackgroundColor,
256 + title,
257 + text,
258 + defaultAction,
259 + actions },
260 +
261 +
262 +
263 +
264 +
265 +
266 +
267 +
268 +
269 +
270 +options = {})
271 +{
272 + return createTemplate(
273 + altText,
274 + {
275 + type: 'buttons',
276 + thumbnailImageUrl,
277 + imageAspectRatio,
278 + imageSize,
279 + imageBackgroundColor,
280 + title,
281 + text,
282 + defaultAction,
283 + actions },
284 +
285 + (0, _lodash.default)(options, 'accessToken'));
286 +
287 +}
288 +
289 +function createConfirmTemplate(
290 +altText,
291 +{
292 + text,
293 + actions },
294 +
295 +
296 +
297 +
298 +options = {})
299 +{
300 + return createTemplate(
301 + altText,
302 + {
303 + type: 'confirm',
304 + text,
305 + actions },
306 +
307 + (0, _lodash.default)(options, 'accessToken'));
308 +
309 +}
310 +
311 +function createCarouselTemplate(
312 +altText,
313 +columns,
314 +{
315 + imageAspectRatio,
316 + imageSize,
317 + quickReply } =
318 +
319 +
320 +
321 +
322 +{})
323 +{
324 + return createTemplate(
325 + altText,
326 + {
327 + type: 'carousel',
328 + columns,
329 + imageAspectRatio,
330 + imageSize },
331 +
332 + { quickReply });
333 +
334 +}
335 +
336 +function createImageCarouselTemplate(
337 +altText,
338 +columns,
339 +options = {})
340 +{
341 + return createTemplate(
342 + altText,
343 + {
344 + type: 'image_carousel',
345 + columns },
346 +
347 + (0, _lodash.default)(options, 'accessToken'));
348 +
349 +}
350 +
351 +function createFlex(
352 +altText,
353 +contents,
354 +options = {})
355 +{
356 + return _objectSpread({
357 + type: 'flex',
358 + altText,
359 + contents },
360 + (0, _lodash.default)(options, 'accessToken'));
361 +
362 +}
363 +
364 +const Line = {
365 + createText,
366 + createImage,
367 + createVideo,
368 + createAudio,
369 + createLocation,
370 + createSticker,
371 + createImagemap,
372 + createTemplate,
373 + createButtonsTemplate: createButtonTemplate,
374 + createButtonTemplate,
375 + createConfirmTemplate,
376 + createCarouselTemplate,
377 + createImageCarouselTemplate,
378 + createFlex };var _default =
379 +
380 +
381 +Line;exports.default = _default;
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.default = void 0;
2 +var _axiosError = _interopRequireDefault(require("axios-error"));
3 +var _axios = _interopRequireDefault(require("axios"));
4 +var _debug = _interopRequireDefault(require("debug"));
5 +var _imageType = _interopRequireDefault(require("image-type"));
6 +var _invariant = _interopRequireDefault(require("invariant"));
7 +var _lodash = _interopRequireDefault(require("lodash.omit"));
8 +var _urlJoin = _interopRequireDefault(require("url-join"));
9 +
10 +var _Line = _interopRequireDefault(require("./Line"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function _objectWithoutProperties(source, excluded) {if (source == null) return {};var target = _objectWithoutPropertiesLoose(source, excluded);var key, i;if (Object.getOwnPropertySymbols) {var sourceSymbolKeys = Object.getOwnPropertySymbols(source);for (i = 0; i < sourceSymbolKeys.length; i++) {key = sourceSymbolKeys[i];if (excluded.indexOf(key) >= 0) continue;if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;target[key] = source[key];}}return target;}function _objectWithoutPropertiesLoose(source, excluded) {if (source == null) return {};var target = {};var sourceKeys = Object.keys(source);var key, i;for (i = 0; i < sourceKeys.length; i++) {key = sourceKeys[i];if (excluded.indexOf(key) >= 0) continue;target[key] = source[key];}return target;}function ownKeys(object, enumerableOnly) {var keys = Object.keys(object);if (Object.getOwnPropertySymbols) {var symbols = Object.getOwnPropertySymbols(object);if (enumerableOnly) symbols = symbols.filter(function (sym) {return Object.getOwnPropertyDescriptor(object, sym).enumerable;});keys.push.apply(keys, symbols);}return keys;}function _objectSpread(target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i] != null ? arguments[i] : {};if (i % 2) {ownKeys(source, true).forEach(function (key) {_defineProperty(target, key, source[key]);});} else if (Object.getOwnPropertyDescriptors) {Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));} else {ownKeys(source).forEach(function (key) {Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));});}}return target;}function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;}
11 +
12 +
13 +
14 +
15 +
16 +
17 +
18 +
19 +
20 +
21 +
22 +
23 +
24 +
25 +
26 +
27 +
28 +
29 +
30 +
31 +
32 +
33 +
34 +
35 +
36 +
37 +
38 +
39 +
40 +
41 +
42 +
43 +
44 +
45 +
46 +
47 +function handleError(err) {
48 + if (err.response && err.response.data) {
49 + const { message, details } = err.response.data;
50 + let msg = `LINE API - ${message}`;
51 + if (details && details.length > 0) {
52 + details.forEach(detail => {
53 + msg += `\n- ${detail.property}: ${detail.message}`;
54 + });
55 + }
56 + throw new _axiosError.default(msg, err);
57 + }
58 + throw new _axiosError.default(err.message, err);
59 +}
60 +
61 +const debugRequest = (0, _debug.default)('messaging-api-line');
62 +
63 +function onRequest({ method, url, body }) {
64 + debugRequest(`${method} ${url}`);
65 + if (body) {
66 + debugRequest('Outgoing request body:');
67 + debugRequest(JSON.stringify(body, null, 2));
68 + }
69 +}
70 +
71 +class LineClient {
72 + static connect(
73 + accessTokenOrConfig,
74 + channelSecret)
75 + {
76 + return new LineClient(accessTokenOrConfig, channelSecret);
77 + }
78 +
79 +
80 +
81 +
82 +
83 +
84 +
85 +
86 +
87 + constructor(
88 + accessTokenOrConfig,
89 + channelSecret)
90 + {_defineProperty(this, "_accessToken", void 0);_defineProperty(this, "_channelSecret", void 0);_defineProperty(this, "_onRequest", void 0);_defineProperty(this, "_axios", void 0);
91 + let origin;
92 + if (accessTokenOrConfig && typeof accessTokenOrConfig === 'object') {
93 + const config = accessTokenOrConfig;
94 +
95 + this._accessToken = config.accessToken;
96 + this._channelSecret = config.channelSecret;
97 + this._onRequest = config.onRequest || onRequest;
98 + origin = config.origin;
99 + } else {
100 + this._accessToken = accessTokenOrConfig;
101 + this._channelSecret = channelSecret;
102 + this._onRequest = onRequest;
103 + }
104 +
105 + this._axios = _axios.default.create({
106 + baseURL: `${origin || 'https://api.line.me'}/`,
107 + headers: {
108 + Authorization: `Bearer ${this._accessToken}`,
109 + 'Content-Type': 'application/json' } });
110 +
111 +
112 +
113 + this._axios.interceptors.request.use(config => {
114 + this._onRequest({
115 + method: config.method,
116 + url: (0, _urlJoin.default)(config.baseURL, config.url),
117 + headers: _objectSpread({},
118 + config.headers.common, {},
119 + config.headers[config.method], {},
120 + (0, _lodash.default)(config.headers, [
121 + 'common',
122 + 'get',
123 + 'post',
124 + 'put',
125 + 'patch',
126 + 'delete',
127 + 'head'])),
128 +
129 +
130 + body: config.data });
131 +
132 + return config;
133 + });
134 + }
135 +
136 + get axios() {
137 + return this._axios;
138 + }
139 +
140 + get accessToken() {
141 + return this._accessToken;
142 + }
143 +
144 + _send(
145 + type,
146 + target,
147 + ...args)
148 + {
149 + if (type === 'push') {
150 + return this.push(target, ...args);
151 + }
152 + if (type === 'multicast') {
153 + return this.multicast(target, ...args);
154 + }
155 + return this.reply(target, ...args);
156 + }
157 +
158 + _sendText(
159 + type,
160 + target,
161 + text,
162 + options)
163 + {
164 + return this._send(
165 + type,
166 + target,
167 + [_Line.default.createText(text, options || {})],
168 + options);
169 +
170 + }
171 +
172 + _sendImage(
173 + type,
174 + target,
175 + contentUrlOrImage,
176 + previewUrlOrOptions)
177 + {
178 + return this._send(
179 + type,
180 + target,
181 + [_Line.default.createImage(contentUrlOrImage, previewUrlOrOptions)],
182 + typeof previewUrlOrOptions === 'string' ? undefined : previewUrlOrOptions);
183 +
184 + }
185 +
186 + _sendVideo(
187 + type,
188 + target,
189 + contentUrlOrVideo,
190 + previewUrlOrOptions)
191 + {
192 + return this._send(
193 + type,
194 + target,
195 + [_Line.default.createVideo(contentUrlOrVideo, previewUrlOrOptions || {})],
196 + typeof previewUrlOrOptions === 'string' ? undefined : previewUrlOrOptions);
197 +
198 + }
199 +
200 + _sendAudio(
201 + type,
202 + target,
203 + contentUrlOrAudio,
204 + durationOrOptions)
205 + {
206 + return this._send(
207 + type,
208 + target,
209 + [_Line.default.createAudio(contentUrlOrAudio, durationOrOptions || {})],
210 + typeof durationOrOptions === 'number' ? undefined : durationOrOptions);
211 +
212 + }
213 +
214 + _sendLocation(
215 + type,
216 + target,
217 + { title, address, latitude, longitude },
218 + options)
219 + {
220 + return this._send(
221 + type,
222 + target,
223 + [
224 + _Line.default.createLocation(
225 + {
226 + title,
227 + address,
228 + latitude,
229 + longitude },
230 +
231 + options || {})],
232 +
233 +
234 + options);
235 +
236 + }
237 +
238 + _sendSticker(
239 + type,
240 + target,
241 + packageIdOrSticker,
242 + stickerIdOrOptions)
243 + {
244 + return this._send(
245 + type,
246 + target,
247 + [_Line.default.createSticker(packageIdOrSticker, stickerIdOrOptions || {})],
248 + typeof stickerIdOrOptions === 'string' ? undefined : stickerIdOrOptions);
249 +
250 + }
251 +
252 + /**
253 + * Imagemap Message
254 + *
255 + * https://developers.line.me/en/docs/messaging-api/reference/#imagemap-message
256 + */
257 + _sendImagemap(
258 + type,
259 + target,
260 + altText,
261 + {
262 + baseUrl,
263 + baseSize,
264 + baseHeight,
265 + baseWidth,
266 + video,
267 + actions },
268 +
269 +
270 +
271 +
272 +
273 +
274 +
275 +
276 +
277 +
278 +
279 + options)
280 + {
281 + return this._send(
282 + type,
283 + target,
284 + [
285 + _Line.default.createImagemap(
286 + altText,
287 + {
288 + baseUrl,
289 + baseSize,
290 + baseHeight,
291 + baseWidth,
292 + video,
293 + actions },
294 +
295 + options || {})],
296 +
297 +
298 + options);
299 +
300 + }
301 +
302 + /**
303 + * Flex Message
304 + *
305 + * https://developers.line.me/en/docs/messaging-api/reference/#flex-message
306 + */
307 + _sendFlex(
308 + type,
309 + target,
310 + altText,
311 + contents,
312 + options)
313 + {
314 + return this._send(
315 + type,
316 + target,
317 + [_Line.default.createFlex(altText, contents, options || {})],
318 + options);
319 +
320 + }
321 +
322 + /**
323 + * Template Messages
324 + *
325 + * https://developers.line.me/en/docs/messaging-api/reference/#template-messages
326 + */
327 + _sendTemplate(
328 + type,
329 + target,
330 + altText,
331 + template,
332 + options)
333 + {
334 + return this._send(
335 + type,
336 + target,
337 + [_Line.default.createTemplate(altText, template, options || {})],
338 + options);
339 +
340 + }
341 +
342 + _sendButtonTemplate(
343 + type,
344 + target,
345 + altText,
346 + {
347 + thumbnailImageUrl,
348 + imageAspectRatio,
349 + imageSize,
350 + imageBackgroundColor,
351 + title,
352 + text,
353 + defaultAction,
354 + actions },
355 +
356 +
357 +
358 +
359 +
360 +
361 +
362 +
363 +
364 +
365 + options)
366 + {
367 + return this._send(
368 + type,
369 + target,
370 + [
371 + _Line.default.createButtonTemplate(
372 + altText,
373 + {
374 + thumbnailImageUrl,
375 + imageAspectRatio,
376 + imageSize,
377 + imageBackgroundColor,
378 + title,
379 + text,
380 + defaultAction,
381 + actions },
382 +
383 + options || {})],
384 +
385 +
386 + options);
387 +
388 + }
389 +
390 + _sendConfirmTemplate(
391 + type,
392 + target,
393 + altText,
394 + {
395 + text,
396 + actions },
397 +
398 +
399 +
400 +
401 + options)
402 + {
403 + return this._send(
404 + type,
405 + target,
406 + [
407 + _Line.default.createConfirmTemplate(
408 + altText,
409 + {
410 + text,
411 + actions },
412 +
413 + options || {})],
414 +
415 +
416 + options);
417 +
418 + }
419 +
420 + _sendCarouselTemplate(
421 + type,
422 + target,
423 + altText,
424 + columns,
425 + _ref =
426 +
427 +
428 +
429 +
430 +
431 +
432 +
433 + {})
434 + {let { imageAspectRatio, imageSize } = _ref,options = _objectWithoutProperties(_ref, ["imageAspectRatio", "imageSize"]);
435 + return this._send(
436 + type,
437 + target,
438 + [
439 + _Line.default.createCarouselTemplate(altText, columns, _objectSpread({
440 + imageAspectRatio,
441 + imageSize },
442 + options))],
443 +
444 +
445 + options);
446 +
447 + }
448 +
449 + _sendImageCarouselTemplate(
450 + type,
451 + target,
452 + altText,
453 + columns,
454 + options)
455 + {
456 + return this._send(
457 + type,
458 + target,
459 + [_Line.default.createImageCarouselTemplate(altText, columns, options || {})],
460 + options);
461 +
462 + }
463 +
464 + /**
465 + * Reply Message
466 + *
467 + * https://developers.line.me/en/docs/messaging-api/reference/#send-reply-message
468 + */
469 + replyRawBody(
470 + body,
471 +
472 +
473 +
474 + { accessToken: customAccessToken } = {})
475 + {
476 + return this._axios.
477 + post(
478 + '/v2/bot/message/reply',
479 + body,
480 + customAccessToken && {
481 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
482 +
483 +
484 + then(res => res.data, handleError);
485 + }
486 +
487 + reply(
488 + replyToken,
489 + messages,
490 + options = {})
491 + {
492 + return this.replyRawBody({ replyToken, messages }, options);
493 + }
494 +
495 + /**
496 + * Push Message
497 + *
498 + * https://developers.line.me/en/docs/messaging-api/reference/#send-push-message
499 + */
500 + pushRawBody(
501 + body,
502 +
503 +
504 +
505 + { accessToken: customAccessToken } = {})
506 + {
507 + return this._axios.
508 + post(
509 + '/v2/bot/message/push',
510 + body,
511 + customAccessToken && {
512 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
513 +
514 +
515 + then(res => res.data, handleError);
516 + }
517 +
518 + push(
519 + to,
520 + messages,
521 + options = {})
522 + {
523 + return this.pushRawBody({ to, messages }, options);
524 + }
525 +
526 + /**
527 + * Multicast
528 + *
529 + * https://developers.line.me/en/docs/messaging-api/reference/#send-multicast-messages
530 + */
531 + multicastRawBody(
532 + body,
533 +
534 +
535 +
536 + { accessToken: customAccessToken } = {})
537 + {
538 + return this._axios.
539 + post(
540 + '/v2/bot/message/multicast',
541 + body,
542 + customAccessToken && {
543 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
544 +
545 +
546 + then(res => res.data, handleError);
547 + }
548 +
549 + multicast(
550 + to,
551 + messages,
552 + options = {})
553 + {
554 + return this.multicastRawBody({ to, messages }, options);
555 + }
556 +
557 + /**
558 + * Content
559 + *
560 + * https://developers.line.me/en/docs/messaging-api/reference/#get-content
561 + */
562 + retrieveMessageContent(
563 + messageId,
564 + { accessToken: customAccessToken } = {})
565 + {
566 + return this._axios.
567 + get(`/v2/bot/message/${messageId}/content`, _objectSpread({
568 + responseType: 'arraybuffer' },
569 + customAccessToken ?
570 + { headers: { Authorization: `Bearer ${customAccessToken}` } } :
571 + undefined)).
572 +
573 + then(res => res.data, handleError);
574 + }
575 +
576 + /**
577 + * Get User Profile
578 + *
579 + * https://developers.line.me/en/docs/messaging-api/reference/#get-profile
580 + * displayName, userId, pictureUrl, statusMessage
581 + */
582 + getUserProfile(
583 + userId,
584 + { accessToken: customAccessToken } = {})
585 + {
586 + return this._axios.
587 + get(
588 + `/v2/bot/profile/${userId}`,
589 + customAccessToken && {
590 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
591 +
592 +
593 + then(res => res.data, handleError).
594 + catch(err => {
595 + if (err.response && err.response.status === 404) {
596 + return null;
597 + }
598 + handleError(err);
599 + });
600 + }
601 +
602 + /**
603 + * Get Group Member Profile
604 + *
605 + * https://developers.line.me/en/docs/messaging-api/reference/#get-group-member-profile
606 + */
607 + getGroupMemberProfile(
608 + groupId,
609 + userId,
610 + { accessToken: customAccessToken } = {})
611 + {
612 + return this._axios.
613 + get(
614 + `/v2/bot/group/${groupId}/member/${userId}`,
615 + customAccessToken && {
616 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
617 +
618 +
619 + then(res => res.data, handleError);
620 + }
621 +
622 + /**
623 + * Get Room Member Profile
624 + *
625 + * https://developers.line.me/en/docs/messaging-api/reference/#get-room-member-profile
626 + */
627 + getRoomMemberProfile(
628 + roomId,
629 + userId,
630 + { accessToken: customAccessToken } = {})
631 + {
632 + return this._axios.
633 + get(
634 + `/v2/bot/room/${roomId}/member/${userId}`,
635 + customAccessToken && {
636 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
637 +
638 +
639 + then(res => res.data, handleError);
640 + }
641 +
642 + /**
643 + * Get Group Member IDs
644 + *
645 + * https://developers.line.me/en/docs/messaging-api/reference/#get-group-member-user-ids
646 + */
647 + getGroupMemberIds(
648 + groupId,
649 + start,
650 + { accessToken: customAccessToken } = {})
651 + {
652 + return this._axios.
653 + get(
654 + `/v2/bot/group/${groupId}/members/ids${start ? `?start=${start}` : ''}`,
655 + customAccessToken && {
656 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
657 +
658 +
659 + then(res => res.data, handleError);
660 + }
661 +
662 + async getAllGroupMemberIds(
663 + groupId,
664 + options = {})
665 + {
666 + let allMemberIds = [];
667 + let continuationToken;
668 +
669 + do {
670 + // eslint-disable-next-line no-await-in-loop
671 + const { memberIds, next } = await this.getGroupMemberIds(
672 + groupId,
673 + continuationToken,
674 + options);
675 +
676 + allMemberIds = allMemberIds.concat(memberIds);
677 + continuationToken = next;
678 + } while (continuationToken);
679 +
680 + return allMemberIds;
681 + }
682 +
683 + /**
684 + * Get Room Member IDs
685 + *
686 + * https://developers.line.me/en/docs/messaging-api/reference/#get-room-member-user-ids
687 + */
688 + getRoomMemberIds(
689 + roomId,
690 + start,
691 + { accessToken: customAccessToken } = {})
692 + {
693 + return this._axios.
694 + get(
695 + `/v2/bot/room/${roomId}/members/ids${start ? `?start=${start}` : ''}`,
696 + customAccessToken && {
697 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
698 +
699 +
700 + then(res => res.data, handleError);
701 + }
702 +
703 + async getAllRoomMemberIds(
704 + roomId,
705 + options = {})
706 + {
707 + let allMemberIds = [];
708 + let continuationToken;
709 +
710 + do {
711 + // eslint-disable-next-line no-await-in-loop
712 + const { memberIds, next } = await this.getRoomMemberIds(
713 + roomId,
714 + continuationToken,
715 + options);
716 +
717 + allMemberIds = allMemberIds.concat(memberIds);
718 + continuationToken = next;
719 + } while (continuationToken);
720 +
721 + return allMemberIds;
722 + }
723 +
724 + /**
725 + * Leave Group
726 + *
727 + * https://developers.line.me/en/docs/messaging-api/reference/#leave-group
728 + */
729 + leaveGroup(
730 + groupId,
731 + { accessToken: customAccessToken } = {})
732 + {
733 + return this._axios.
734 + post(
735 + `/v2/bot/group/${groupId}/leave`,
736 + null,
737 + customAccessToken && {
738 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
739 +
740 +
741 + then(res => res.data, handleError);
742 + }
743 +
744 + /**
745 + * Leave Room
746 + *
747 + * https://developers.line.me/en/docs/messaging-api/reference/#leave-room
748 + */
749 + leaveRoom(
750 + roomId,
751 + { accessToken: customAccessToken } = {})
752 + {
753 + return this._axios.
754 + post(
755 + `/v2/bot/room/${roomId}/leave`,
756 + null,
757 + customAccessToken && {
758 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
759 +
760 +
761 + then(res => res.data, handleError);
762 + }
763 +
764 + /**
765 + * Rich Menu
766 + *
767 + * https://developers.line.me/en/docs/messaging-api/reference/#rich-menu
768 + */
769 + getRichMenuList({
770 + accessToken: customAccessToken } =
771 + {}) {
772 + return this._axios.
773 + get(
774 + '/v2/bot/richmenu/list',
775 + customAccessToken && {
776 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
777 +
778 +
779 + then(res => res.data.richmenus, handleError);
780 + }
781 +
782 + getRichMenu(
783 + richMenuId,
784 + { accessToken: customAccessToken } = {})
785 + {
786 + return this._axios.
787 + get(
788 + `/v2/bot/richmenu/${richMenuId}`,
789 + customAccessToken && {
790 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
791 +
792 +
793 + then(res => res.data).
794 + catch(err => {
795 + if (err.response && err.response.status === 404) {
796 + return null;
797 + }
798 + handleError(err);
799 + });
800 + }
801 +
802 + createRichMenu(
803 + richMenu,
804 + { accessToken: customAccessToken } = {})
805 + {
806 + return this._axios.
807 + post(
808 + '/v2/bot/richmenu',
809 + richMenu,
810 + customAccessToken && {
811 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
812 +
813 +
814 + then(res => res.data, handleError);
815 + }
816 +
817 + deleteRichMenu(
818 + richMenuId,
819 + { accessToken: customAccessToken } = {})
820 + {
821 + return this._axios.
822 + delete(
823 + `/v2/bot/richmenu/${richMenuId}`,
824 + customAccessToken && {
825 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
826 +
827 +
828 + then(res => res.data, handleError);
829 + }
830 +
831 + getLinkedRichMenu(
832 + userId,
833 + { accessToken: customAccessToken } = {})
834 + {
835 + return this._axios.
836 + get(
837 + `/v2/bot/user/${userId}/richmenu`,
838 + customAccessToken && {
839 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
840 +
841 +
842 + then(res => res.data).
843 + catch(err => {
844 + if (err.response && err.response.status === 404) {
845 + return null;
846 + }
847 + handleError(err);
848 + });
849 + }
850 +
851 + linkRichMenu(
852 + userId,
853 + richMenuId,
854 + { accessToken: customAccessToken } = {})
855 + {
856 + return this._axios.
857 + post(
858 + `/v2/bot/user/${userId}/richmenu/${richMenuId}`,
859 + null,
860 + customAccessToken && {
861 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
862 +
863 +
864 + then(res => res.data, handleError);
865 + }
866 +
867 + unlinkRichMenu(
868 + userId,
869 + { accessToken: customAccessToken } = {})
870 + {
871 + return this._axios.
872 + delete(
873 + `/v2/bot/user/${userId}/richmenu`,
874 + customAccessToken && {
875 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
876 +
877 +
878 + then(res => res.data, handleError);
879 + }
880 +
881 + getDefaultRichMenu({
882 + accessToken: customAccessToken } =
883 + {}) {
884 + return this._axios.
885 + get(
886 + `/v2/bot/user/all/richmenu`,
887 + customAccessToken && {
888 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
889 +
890 +
891 + then(res => res.data).
892 + catch(err => {
893 + if (err.response && err.response.status === 404) {
894 + return null;
895 + }
896 + handleError(err);
897 + });
898 + }
899 +
900 + setDefaultRichMenu(
901 + richMenuId,
902 + { accessToken: customAccessToken } = {})
903 + {
904 + return this._axios.
905 + post(
906 + `/v2/bot/user/all/richmenu/${richMenuId}`,
907 + null,
908 + customAccessToken && {
909 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
910 +
911 +
912 + then(res => res.data, handleError);
913 + }
914 +
915 + deleteDefaultRichMenu({
916 + accessToken: customAccessToken } =
917 + {}) {
918 + return this._axios.
919 + delete(
920 + `/v2/bot/user/all/richmenu`,
921 + customAccessToken && {
922 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
923 +
924 +
925 + then(res => res.data, handleError);
926 + }
927 +
928 + /**
929 + * - Images must have one of the following resolutions: 2500x1686, 2500x843.
930 + * - You cannot replace an image attached to a rich menu.
931 + * To update your rich menu image, create a new rich menu object and upload another image.
932 + */
933 + uploadRichMenuImage(
934 + richMenuId,
935 + image,
936 + { accessToken: customAccessToken } = {})
937 + {
938 + const type = (0, _imageType.default)(image);
939 + (0, _invariant.default)(
940 + type && (type.mime === 'image/jpeg' || type.mime === 'image/png'),
941 + 'Image must be `image/jpeg` or `image/png`');
942 +
943 + return this._axios.
944 + post(`/v2/bot/richmenu/${richMenuId}/content`, image, {
945 + headers: customAccessToken ?
946 + {
947 + 'Content-Type': type.mime,
948 + Authorization: `Bearer ${customAccessToken}` } :
949 +
950 + {
951 + 'Content-Type': type.mime } }).
952 +
953 +
954 + then(res => res.data, handleError);
955 + }
956 +
957 + downloadRichMenuImage(
958 + richMenuId,
959 + { accessToken: customAccessToken } = {})
960 + {
961 + return this._axios.
962 + get(
963 + `/v2/bot/richmenu/${richMenuId}/content`,
964 + customAccessToken ?
965 + {
966 + responseType: 'arraybuffer',
967 + headers: {
968 + Authorization: `Bearer ${customAccessToken}` } } :
969 +
970 +
971 + {
972 + responseType: 'arraybuffer' }).
973 +
974 +
975 + then(res => Buffer.from(res.data)).
976 + catch(err => {
977 + if (err.response && err.response.status === 404) {
978 + return null;
979 + }
980 + handleError(err);
981 + });
982 + }
983 +
984 + /**
985 + * Account link
986 + *
987 + * https://developers.line.me/en/docs/messaging-api/reference/#account-link
988 + */
989 +
990 + issueLinkToken(
991 + userId,
992 + { accessToken: customAccessToken } = {})
993 + {
994 + return this._axios.
995 + post(
996 + `/v2/bot/user/${userId}/linkToken`,
997 + null,
998 + customAccessToken && {
999 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
1000 +
1001 +
1002 + then(res => res.data, handleError);
1003 + }
1004 +
1005 + /**
1006 + * LINE Front-end Framework (LIFF)
1007 + *
1008 + * https://developers.line.me/en/docs/liff/reference/#add-liff-app
1009 + */
1010 + getLiffAppList({
1011 + accessToken: customAccessToken } =
1012 + {})
1013 +
1014 +
1015 + {
1016 + return this._axios.
1017 + get(
1018 + '/liff/v1/apps',
1019 + customAccessToken && {
1020 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
1021 +
1022 +
1023 + then(res => res.data.apps, handleError);
1024 + }
1025 +
1026 + createLiffApp(
1027 + view,
1028 + { accessToken: customAccessToken } = {})
1029 + {
1030 + return this._axios.
1031 + post(
1032 + '/liff/v1/apps',
1033 + view,
1034 + customAccessToken && {
1035 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
1036 +
1037 +
1038 + then(res => res.data, handleError);
1039 + }
1040 +
1041 + updateLiffApp(
1042 + liffId,
1043 + view,
1044 + { accessToken: customAccessToken } = {})
1045 + {
1046 + return this._axios.
1047 + put(
1048 + `/liff/v1/apps/${liffId}/view`,
1049 + view,
1050 + customAccessToken && {
1051 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
1052 +
1053 +
1054 + then(res => res.data, handleError);
1055 + }
1056 +
1057 + deleteLiffApp(
1058 + liffId,
1059 + { accessToken: customAccessToken } = {})
1060 + {
1061 + return this._axios.
1062 + delete(
1063 + `/liff/v1/apps/${liffId}`,
1064 + customAccessToken && {
1065 + headers: { Authorization: `Bearer ${customAccessToken}` } }).
1066 +
1067 +
1068 + then(res => res.data, handleError);
1069 + }}exports.default = LineClient;
1070 +
1071 +
1072 +const sendTypes = ['reply', 'push', 'multicast'];
1073 +
1074 +const messageTypes =
1075 +
1076 +
1077 +[
1078 +{ name: 'Text' },
1079 +{ name: 'Image' },
1080 +{ name: 'Video' },
1081 +{ name: 'Audio' },
1082 +{ name: 'Location' },
1083 +{ name: 'Sticker' },
1084 +{ name: 'Imagemap' },
1085 +{ name: 'Flex' },
1086 +{ name: 'Template' },
1087 +{ name: 'ButtonTemplate', aliases: ['ButtonsTemplate'] },
1088 +{ name: 'ConfirmTemplate' },
1089 +{ name: 'CarouselTemplate' },
1090 +{ name: 'ImageCarouselTemplate' }];
1091 +
1092 +
1093 +messageTypes.forEach(({ name, aliases }) => {
1094 + sendTypes.forEach(sendType => {
1095 + [name].concat(aliases || []).forEach(type => {
1096 + Object.defineProperty(LineClient.prototype, `${sendType}${type}`, {
1097 + enumerable: false,
1098 + configurable: true,
1099 + writable: true,
1100 + value(target, ...args) {
1101 + return this[`_send${name}`](sendType, target, ...args);
1102 + } });
1103 +
1104 + });
1105 + });
1106 +});
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.default = void 0;
2 +var _querystring = _interopRequireDefault(require("querystring"));
3 +
4 +var _axiosError = _interopRequireDefault(require("axios-error"));
5 +var _axios = _interopRequireDefault(require("axios"));
6 +var _invariant = _interopRequireDefault(require("invariant"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}function ownKeys(object, enumerableOnly) {var keys = Object.keys(object);if (Object.getOwnPropertySymbols) {var symbols = Object.getOwnPropertySymbols(object);if (enumerableOnly) symbols = symbols.filter(function (sym) {return Object.getOwnPropertyDescriptor(object, sym).enumerable;});keys.push.apply(keys, symbols);}return keys;}function _objectSpread(target) {for (var i = 1; i < arguments.length; i++) {var source = arguments[i] != null ? arguments[i] : {};if (i % 2) {ownKeys(source, true).forEach(function (key) {_defineProperty(target, key, source[key]);});} else if (Object.getOwnPropertyDescriptors) {Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));} else {ownKeys(source).forEach(function (key) {Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));});}}return target;}function _objectWithoutProperties(source, excluded) {if (source == null) return {};var target = _objectWithoutPropertiesLoose(source, excluded);var key, i;if (Object.getOwnPropertySymbols) {var sourceSymbolKeys = Object.getOwnPropertySymbols(source);for (i = 0; i < sourceSymbolKeys.length; i++) {key = sourceSymbolKeys[i];if (excluded.indexOf(key) >= 0) continue;if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;target[key] = source[key];}}return target;}function _objectWithoutPropertiesLoose(source, excluded) {if (source == null) return {};var target = {};var sourceKeys = Object.keys(source);var key, i;for (i = 0; i < sourceKeys.length; i++) {key = sourceKeys[i];if (excluded.indexOf(key) >= 0) continue;target[key] = source[key];}return target;}function _defineProperty(obj, key, value) {if (key in obj) {Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });} else {obj[key] = value;}return obj;}
7 +
8 +
9 +
10 +
11 +
12 +
13 +
14 +
15 +
16 +
17 +
18 +
19 +
20 +
21 +
22 +
23 +
24 +
25 +function handleError(err) {
26 + if (err.response && err.response.data) {
27 + const { returnCode, returnMessage } = err.response.data;
28 + const msg = `LINE PAY API - ${returnCode} ${returnMessage}`;
29 + throw new _axiosError.default(msg, err);
30 + }
31 + throw new _axiosError.default(err.message, err);
32 +}
33 +
34 +function throwWhenNotSuccess(res) {
35 + if (res.data.returnCode !== '0000') {
36 + const { returnCode, returnMessage } = res.data;
37 + const msg = `LINE PAY API - ${returnCode} ${returnMessage}`;
38 + throw new _axiosError.default(msg);
39 + }
40 + return res.data.info;
41 +}
42 +
43 +class LinePay {
44 + static connect(config) {
45 + return new LinePay(config);
46 + }
47 +
48 +
49 +
50 + constructor({
51 + channelId,
52 + channelSecret,
53 + sandbox = false,
54 + origin })
55 + {_defineProperty(this, "_axios", void 0);
56 + const linePayOrigin = sandbox ?
57 + 'https://sandbox-api-pay.line.me' :
58 + 'https://api-pay.line.me';
59 +
60 + this._axios = _axios.default.create({
61 + baseURL: `${origin || linePayOrigin}/v2/`,
62 + headers: {
63 + 'Content-Type': 'application/json',
64 + 'X-LINE-ChannelId': channelId,
65 + 'X-LINE-ChannelSecret': channelSecret } });
66 +
67 +
68 + }
69 +
70 + get axios() {
71 + return this._axios;
72 + }
73 +
74 + getPayments({
75 + transactionId,
76 + orderId } =
77 +
78 +
79 +
80 + {}) {
81 + (0, _invariant.default)(
82 + transactionId || orderId,
83 + 'getPayments: One of `transactionId` or `orderId` must be provided');
84 +
85 +
86 + const query = {};
87 +
88 + if (transactionId) {
89 + query.transactionId = transactionId;
90 + }
91 +
92 + if (orderId) {
93 + query.orderId = orderId;
94 + }
95 +
96 + return this._axios.
97 + get(`/payments?${_querystring.default.stringify(query)}`).
98 + then(throwWhenNotSuccess, handleError);
99 + }
100 +
101 + getAuthorizations({
102 + transactionId,
103 + orderId } =
104 +
105 +
106 +
107 + {}) {
108 + (0, _invariant.default)(
109 + transactionId || orderId,
110 + 'getAuthorizations: One of `transactionId` or `orderId` must be provided');
111 +
112 +
113 + const query = {};
114 +
115 + if (transactionId) {
116 + query.transactionId = transactionId;
117 + }
118 +
119 + if (orderId) {
120 + query.orderId = orderId;
121 + }
122 +
123 + return this._axios.
124 + get(`/payments/authorizations?${_querystring.default.stringify(query)}`).
125 + then(throwWhenNotSuccess, handleError);
126 + }
127 +
128 + reserve(_ref)
129 +
130 +
131 +
132 +
133 +
134 +
135 +
136 +
137 +
138 +
139 +
140 +
141 +
142 +
143 +
144 +
145 +
146 +
147 +
148 +
149 +
150 +
151 +
152 +
153 + {let { productName, amount, currency, confirmUrl, orderId } = _ref,options = _objectWithoutProperties(_ref, ["productName", "amount", "currency", "confirmUrl", "orderId"]);
154 + return this._axios.
155 + post('/payments/request', _objectSpread({
156 + productName,
157 + amount,
158 + currency,
159 + confirmUrl,
160 + orderId },
161 + options)).
162 +
163 + then(throwWhenNotSuccess, handleError);
164 + }
165 +
166 + confirm(
167 + transactionId,
168 + {
169 + amount,
170 + currency })
171 +
172 +
173 +
174 +
175 + {
176 + return this._axios.
177 + post(`/payments/${transactionId}/confirm`, {
178 + amount,
179 + currency }).
180 +
181 + then(throwWhenNotSuccess, handleError);
182 + }
183 +
184 + capture(
185 + transactionId,
186 + {
187 + amount,
188 + currency })
189 +
190 +
191 +
192 +
193 + {
194 + return this._axios.
195 + post(`/payments/authorizations/${transactionId}/capture`, {
196 + amount,
197 + currency }).
198 +
199 + then(throwWhenNotSuccess, handleError);
200 + }
201 +
202 + void(transactionId) {
203 + return this._axios.
204 + post(`/payments/authorizations/${transactionId}/void`).
205 + then(throwWhenNotSuccess, handleError);
206 + }
207 +
208 + refund(transactionId, options = {}) {
209 + return this._axios.
210 + post(`/payments/${transactionId}/refund`, options).
211 + then(throwWhenNotSuccess, handleError);
212 + }}exports.default = LinePay;
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";Object.defineProperty(exports, "__esModule", { value: true });Object.defineProperty(exports, "Line", { enumerable: true, get: function () {return _Line.default;} });exports.default = void 0;
2 +
3 +var _Line = _interopRequireDefault(require("./Line"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}var _default =
4 +
5 +
6 +{ Line: _Line.default };exports.default = _default;
...\ No newline at end of file ...\ No newline at end of file
1 +"use strict";Object.defineProperty(exports, "__esModule", { value: true });Object.defineProperty(exports, "Line", { enumerable: true, get: function () {return _Line.default;} });Object.defineProperty(exports, "LineClient", { enumerable: true, get: function () {return _LineClient.default;} });Object.defineProperty(exports, "LinePay", { enumerable: true, get: function () {return _LinePay.default;} });exports.default = void 0;
2 +
3 +var _Line = _interopRequireDefault(require("./Line"));
4 +var _LineClient = _interopRequireDefault(require("./LineClient"));
5 +var _LinePay = _interopRequireDefault(require("./LinePay"));function _interopRequireDefault(obj) {return obj && obj.__esModule ? obj : { default: obj };}var _default =
6 +
7 +
8 +{ Line: _Line.default, LineClient: _LineClient.default, LinePay: _LinePay.default };exports.default = _default;
...\ No newline at end of file ...\ No newline at end of file
1 +
2 +3.1.0 / 2017-09-26
3 +==================
4 +
5 + * Add `DEBUG_HIDE_DATE` env var (#486)
6 + * Remove ReDoS regexp in %o formatter (#504)
7 + * Remove "component" from package.json
8 + * Remove `component.json`
9 + * Ignore package-lock.json
10 + * Examples: fix colors printout
11 + * Fix: browser detection
12 + * Fix: spelling mistake (#496, @EdwardBetts)
13 +
14 +3.0.1 / 2017-08-24
15 +==================
16 +
17 + * Fix: Disable colors in Edge and Internet Explorer (#489)
18 +
19 +3.0.0 / 2017-08-08
20 +==================
21 +
22 + * Breaking: Remove DEBUG_FD (#406)
23 + * Breaking: Use `Date#toISOString()` instead to `Date#toUTCString()` when output is not a TTY (#418)
24 + * Breaking: Make millisecond timer namespace specific and allow 'always enabled' output (#408)
25 + * Addition: document `enabled` flag (#465)
26 + * Addition: add 256 colors mode (#481)
27 + * Addition: `enabled()` updates existing debug instances, add `destroy()` function (#440)
28 + * Update: component: update "ms" to v2.0.0
29 + * Update: separate the Node and Browser tests in Travis-CI
30 + * Update: refactor Readme, fixed documentation, added "Namespace Colors" section, redid screenshots
31 + * Update: separate Node.js and web browser examples for organization
32 + * Update: update "browserify" to v14.4.0
33 + * Fix: fix Readme typo (#473)
34 +
35 +2.6.9 / 2017-09-22
36 +==================
37 +
38 + * remove ReDoS regexp in %o formatter (#504)
39 +
40 +2.6.8 / 2017-05-18
41 +==================
42 +
43 + * Fix: Check for undefined on browser globals (#462, @marbemac)
44 +
45 +2.6.7 / 2017-05-16
46 +==================
47 +
48 + * Fix: Update ms to 2.0.0 to fix regular expression denial of service vulnerability (#458, @hubdotcom)
49 + * Fix: Inline extend function in node implementation (#452, @dougwilson)
50 + * Docs: Fix typo (#455, @msasad)
51 +
52 +2.6.5 / 2017-04-27
53 +==================
54 +
55 + * Fix: null reference check on window.documentElement.style.WebkitAppearance (#447, @thebigredgeek)
56 + * Misc: clean up browser reference checks (#447, @thebigredgeek)
57 + * Misc: add npm-debug.log to .gitignore (@thebigredgeek)
58 +
59 +
60 +2.6.4 / 2017-04-20
61 +==================
62 +
63 + * Fix: bug that would occur if process.env.DEBUG is a non-string value. (#444, @LucianBuzzo)
64 + * Chore: ignore bower.json in npm installations. (#437, @joaovieira)
65 + * Misc: update "ms" to v0.7.3 (@tootallnate)
66 +
67 +2.6.3 / 2017-03-13
68 +==================
69 +
70 + * Fix: Electron reference to `process.env.DEBUG` (#431, @paulcbetts)
71 + * Docs: Changelog fix (@thebigredgeek)
72 +
73 +2.6.2 / 2017-03-10
74 +==================
75 +
76 + * Fix: DEBUG_MAX_ARRAY_LENGTH (#420, @slavaGanzin)
77 + * Docs: Add backers and sponsors from Open Collective (#422, @piamancini)
78 + * Docs: Add Slackin invite badge (@tootallnate)
79 +
80 +2.6.1 / 2017-02-10
81 +==================
82 +
83 + * Fix: Module's `export default` syntax fix for IE8 `Expected identifier` error
84 + * Fix: Whitelist DEBUG_FD for values 1 and 2 only (#415, @pi0)
85 + * Fix: IE8 "Expected identifier" error (#414, @vgoma)
86 + * Fix: Namespaces would not disable once enabled (#409, @musikov)
87 +
88 +2.6.0 / 2016-12-28
89 +==================
90 +
91 + * Fix: added better null pointer checks for browser useColors (@thebigredgeek)
92 + * Improvement: removed explicit `window.debug` export (#404, @tootallnate)
93 + * Improvement: deprecated `DEBUG_FD` environment variable (#405, @tootallnate)
94 +
95 +2.5.2 / 2016-12-25
96 +==================
97 +
98 + * Fix: reference error on window within webworkers (#393, @KlausTrainer)
99 + * Docs: fixed README typo (#391, @lurch)
100 + * Docs: added notice about v3 api discussion (@thebigredgeek)
101 +
102 +2.5.1 / 2016-12-20
103 +==================
104 +
105 + * Fix: babel-core compatibility
106 +
107 +2.5.0 / 2016-12-20
108 +==================
109 +
110 + * Fix: wrong reference in bower file (@thebigredgeek)
111 + * Fix: webworker compatibility (@thebigredgeek)
112 + * Fix: output formatting issue (#388, @kribblo)
113 + * Fix: babel-loader compatibility (#383, @escwald)
114 + * Misc: removed built asset from repo and publications (@thebigredgeek)
115 + * Misc: moved source files to /src (#378, @yamikuronue)
116 + * Test: added karma integration and replaced babel with browserify for browser tests (#378, @yamikuronue)
117 + * Test: coveralls integration (#378, @yamikuronue)
118 + * Docs: simplified language in the opening paragraph (#373, @yamikuronue)
119 +
120 +2.4.5 / 2016-12-17
121 +==================
122 +
123 + * Fix: `navigator` undefined in Rhino (#376, @jochenberger)
124 + * Fix: custom log function (#379, @hsiliev)
125 + * Improvement: bit of cleanup + linting fixes (@thebigredgeek)
126 + * Improvement: rm non-maintainted `dist/` dir (#375, @freewil)
127 + * Docs: simplified language in the opening paragraph. (#373, @yamikuronue)
128 +
129 +2.4.4 / 2016-12-14
130 +==================
131 +
132 + * Fix: work around debug being loaded in preload scripts for electron (#368, @paulcbetts)
133 +
134 +2.4.3 / 2016-12-14
135 +==================
136 +
137 + * Fix: navigation.userAgent error for react native (#364, @escwald)
138 +
139 +2.4.2 / 2016-12-14
140 +==================
141 +
142 + * Fix: browser colors (#367, @tootallnate)
143 + * Misc: travis ci integration (@thebigredgeek)
144 + * Misc: added linting and testing boilerplate with sanity check (@thebigredgeek)
145 +
146 +2.4.1 / 2016-12-13
147 +==================
148 +
149 + * Fix: typo that broke the package (#356)
150 +
151 +2.4.0 / 2016-12-13
152 +==================
153 +
154 + * Fix: bower.json references unbuilt src entry point (#342, @justmatt)
155 + * Fix: revert "handle regex special characters" (@tootallnate)
156 + * Feature: configurable util.inspect()`options for NodeJS (#327, @tootallnate)
157 + * Feature: %O`(big O) pretty-prints objects (#322, @tootallnate)
158 + * Improvement: allow colors in workers (#335, @botverse)
159 + * Improvement: use same color for same namespace. (#338, @lchenay)
160 +
161 +2.3.3 / 2016-11-09
162 +==================
163 +
164 + * Fix: Catch `JSON.stringify()` errors (#195, Jovan Alleyne)
165 + * Fix: Returning `localStorage` saved values (#331, Levi Thomason)
166 + * Improvement: Don't create an empty object when no `process` (Nathan Rajlich)
167 +
168 +2.3.2 / 2016-11-09
169 +==================
170 +
171 + * Fix: be super-safe in index.js as well (@TooTallNate)
172 + * Fix: should check whether process exists (Tom Newby)
173 +
174 +2.3.1 / 2016-11-09
175 +==================
176 +
177 + * Fix: Added electron compatibility (#324, @paulcbetts)
178 + * Improvement: Added performance optimizations (@tootallnate)
179 + * Readme: Corrected PowerShell environment variable example (#252, @gimre)
180 + * Misc: Removed yarn lock file from source control (#321, @fengmk2)
181 +
182 +2.3.0 / 2016-11-07
183 +==================
184 +
185 + * Fix: Consistent placement of ms diff at end of output (#215, @gorangajic)
186 + * Fix: Escaping of regex special characters in namespace strings (#250, @zacronos)
187 + * Fix: Fixed bug causing crash on react-native (#282, @vkarpov15)
188 + * Feature: Enabled ES6+ compatible import via default export (#212 @bucaran)
189 + * Feature: Added %O formatter to reflect Chrome's console.log capability (#279, @oncletom)
190 + * Package: Update "ms" to 0.7.2 (#315, @DevSide)
191 + * Package: removed superfluous version property from bower.json (#207 @kkirsche)
192 + * Readme: fix USE_COLORS to DEBUG_COLORS
193 + * Readme: Doc fixes for format string sugar (#269, @mlucool)
194 + * Readme: Updated docs for DEBUG_FD and DEBUG_COLORS environment variables (#232, @mattlyons0)
195 + * Readme: doc fixes for PowerShell (#271 #243, @exoticknight @unreadable)
196 + * Readme: better docs for browser support (#224, @matthewmueller)
197 + * Tooling: Added yarn integration for development (#317, @thebigredgeek)
198 + * Misc: Renamed History.md to CHANGELOG.md (@thebigredgeek)
199 + * Misc: Added license file (#226 #274, @CantemoInternal @sdaitzman)
200 + * Misc: Updated contributors (@thebigredgeek)
201 +
202 +2.2.0 / 2015-05-09
203 +==================
204 +
205 + * package: update "ms" to v0.7.1 (#202, @dougwilson)
206 + * README: add logging to file example (#193, @DanielOchoa)
207 + * README: fixed a typo (#191, @amir-s)
208 + * browser: expose `storage` (#190, @stephenmathieson)
209 + * Makefile: add a `distclean` target (#189, @stephenmathieson)
210 +
211 +2.1.3 / 2015-03-13
212 +==================
213 +
214 + * Updated stdout/stderr example (#186)
215 + * Updated example/stdout.js to match debug current behaviour
216 + * Renamed example/stderr.js to stdout.js
217 + * Update Readme.md (#184)
218 + * replace high intensity foreground color for bold (#182, #183)
219 +
220 +2.1.2 / 2015-03-01
221 +==================
222 +
223 + * dist: recompile
224 + * update "ms" to v0.7.0
225 + * package: update "browserify" to v9.0.3
226 + * component: fix "ms.js" repo location
227 + * changed bower package name
228 + * updated documentation about using debug in a browser
229 + * fix: security error on safari (#167, #168, @yields)
230 +
231 +2.1.1 / 2014-12-29
232 +==================
233 +
234 + * browser: use `typeof` to check for `console` existence
235 + * browser: check for `console.log` truthiness (fix IE 8/9)
236 + * browser: add support for Chrome apps
237 + * Readme: added Windows usage remarks
238 + * Add `bower.json` to properly support bower install
239 +
240 +2.1.0 / 2014-10-15
241 +==================
242 +
243 + * node: implement `DEBUG_FD` env variable support
244 + * package: update "browserify" to v6.1.0
245 + * package: add "license" field to package.json (#135, @panuhorsmalahti)
246 +
247 +2.0.0 / 2014-09-01
248 +==================
249 +
250 + * package: update "browserify" to v5.11.0
251 + * node: use stderr rather than stdout for logging (#29, @stephenmathieson)
252 +
253 +1.0.4 / 2014-07-15
254 +==================
255 +
256 + * dist: recompile
257 + * example: remove `console.info()` log usage
258 + * example: add "Content-Type" UTF-8 header to browser example
259 + * browser: place %c marker after the space character
260 + * browser: reset the "content" color via `color: inherit`
261 + * browser: add colors support for Firefox >= v31
262 + * debug: prefer an instance `log()` function over the global one (#119)
263 + * Readme: update documentation about styled console logs for FF v31 (#116, @wryk)
264 +
265 +1.0.3 / 2014-07-09
266 +==================
267 +
268 + * Add support for multiple wildcards in namespaces (#122, @seegno)
269 + * browser: fix lint
270 +
271 +1.0.2 / 2014-06-10
272 +==================
273 +
274 + * browser: update color palette (#113, @gscottolson)
275 + * common: make console logging function configurable (#108, @timoxley)
276 + * node: fix %o colors on old node <= 0.8.x
277 + * Makefile: find node path using shell/which (#109, @timoxley)
278 +
279 +1.0.1 / 2014-06-06
280 +==================
281 +
282 + * browser: use `removeItem()` to clear localStorage
283 + * browser, node: don't set DEBUG if namespaces is undefined (#107, @leedm777)
284 + * package: add "contributors" section
285 + * node: fix comment typo
286 + * README: list authors
287 +
288 +1.0.0 / 2014-06-04
289 +==================
290 +
291 + * make ms diff be global, not be scope
292 + * debug: ignore empty strings in enable()
293 + * node: make DEBUG_COLORS able to disable coloring
294 + * *: export the `colors` array
295 + * npmignore: don't publish the `dist` dir
296 + * Makefile: refactor to use browserify
297 + * package: add "browserify" as a dev dependency
298 + * Readme: add Web Inspector Colors section
299 + * node: reset terminal color for the debug content
300 + * node: map "%o" to `util.inspect()`
301 + * browser: map "%j" to `JSON.stringify()`
302 + * debug: add custom "formatters"
303 + * debug: use "ms" module for humanizing the diff
304 + * Readme: add "bash" syntax highlighting
305 + * browser: add Firebug color support
306 + * browser: add colors for WebKit browsers
307 + * node: apply log to `console`
308 + * rewrite: abstract common logic for Node & browsers
309 + * add .jshintrc file
310 +
311 +0.8.1 / 2014-04-14
312 +==================
313 +
314 + * package: re-add the "component" section
315 +
316 +0.8.0 / 2014-03-30
317 +==================
318 +
319 + * add `enable()` method for nodejs. Closes #27
320 + * change from stderr to stdout
321 + * remove unnecessary index.js file
322 +
323 +0.7.4 / 2013-11-13
324 +==================
325 +
326 + * remove "browserify" key from package.json (fixes something in browserify)
327 +
328 +0.7.3 / 2013-10-30
329 +==================
330 +
331 + * fix: catch localStorage security error when cookies are blocked (Chrome)
332 + * add debug(err) support. Closes #46
333 + * add .browser prop to package.json. Closes #42
334 +
335 +0.7.2 / 2013-02-06
336 +==================
337 +
338 + * fix package.json
339 + * fix: Mobile Safari (private mode) is broken with debug
340 + * fix: Use unicode to send escape character to shell instead of octal to work with strict mode javascript
341 +
342 +0.7.1 / 2013-02-05
343 +==================
344 +
345 + * add repository URL to package.json
346 + * add DEBUG_COLORED to force colored output
347 + * add browserify support
348 + * fix component. Closes #24
349 +
350 +0.7.0 / 2012-05-04
351 +==================
352 +
353 + * Added .component to package.json
354 + * Added debug.component.js build
355 +
356 +0.6.0 / 2012-03-16
357 +==================
358 +
359 + * Added support for "-" prefix in DEBUG [Vinay Pulim]
360 + * Added `.enabled` flag to the node version [TooTallNate]
361 +
362 +0.5.0 / 2012-02-02
363 +==================
364 +
365 + * Added: humanize diffs. Closes #8
366 + * Added `debug.disable()` to the CS variant
367 + * Removed padding. Closes #10
368 + * Fixed: persist client-side variant again. Closes #9
369 +
370 +0.4.0 / 2012-02-01
371 +==================
372 +
373 + * Added browser variant support for older browsers [TooTallNate]
374 + * Added `debug.enable('project:*')` to browser variant [TooTallNate]
375 + * Added padding to diff (moved it to the right)
376 +
377 +0.3.0 / 2012-01-26
378 +==================
379 +
380 + * Added millisecond diff when isatty, otherwise UTC string
381 +
382 +0.2.0 / 2012-01-22
383 +==================
384 +
385 + * Added wildcard support
386 +
387 +0.1.0 / 2011-12-02
388 +==================
389 +
390 + * Added: remove colors unless stderr isatty [TooTallNate]
391 +
392 +0.0.1 / 2010-01-03
393 +==================
394 +
395 + * Initial release
1 +(The MIT License)
2 +
3 +Copyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca>
4 +
5 +Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6 +and associated documentation files (the 'Software'), to deal in the Software without restriction,
7 +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
9 +subject to the following conditions:
10 +
11 +The above copyright notice and this permission notice shall be included in all copies or substantial
12 +portions of the Software.
13 +
14 +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15 +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
17 +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 +
1 +# debug
2 +[![Build Status](https://travis-ci.org/visionmedia/debug.svg?branch=master)](https://travis-ci.org/visionmedia/debug) [![Coverage Status](https://coveralls.io/repos/github/visionmedia/debug/badge.svg?branch=master)](https://coveralls.io/github/visionmedia/debug?branch=master) [![Slack](https://visionmedia-community-slackin.now.sh/badge.svg)](https://visionmedia-community-slackin.now.sh/) [![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers)
3 +[![OpenCollective](https://opencollective.com/debug/sponsors/badge.svg)](#sponsors)
4 +
5 +<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
6 +
7 +A tiny JavaScript debugging utility modelled after Node.js core's debugging
8 +technique. Works in Node.js and web browsers.
9 +
10 +## Installation
11 +
12 +```bash
13 +$ npm install debug
14 +```
15 +
16 +## Usage
17 +
18 +`debug` exposes a function; simply pass this function the name of your module, and it will return a decorated version of `console.error` for you to pass debug statements to. This will allow you to toggle the debug output for different parts of your module as well as the module as a whole.
19 +
20 +Example [_app.js_](./examples/node/app.js):
21 +
22 +```js
23 +var debug = require('debug')('http')
24 + , http = require('http')
25 + , name = 'My App';
26 +
27 +// fake app
28 +
29 +debug('booting %o', name);
30 +
31 +http.createServer(function(req, res){
32 + debug(req.method + ' ' + req.url);
33 + res.end('hello\n');
34 +}).listen(3000, function(){
35 + debug('listening');
36 +});
37 +
38 +// fake worker of some kind
39 +
40 +require('./worker');
41 +```
42 +
43 +Example [_worker.js_](./examples/node/worker.js):
44 +
45 +```js
46 +var a = require('debug')('worker:a')
47 + , b = require('debug')('worker:b');
48 +
49 +function work() {
50 + a('doing lots of uninteresting work');
51 + setTimeout(work, Math.random() * 1000);
52 +}
53 +
54 +work();
55 +
56 +function workb() {
57 + b('doing some work');
58 + setTimeout(workb, Math.random() * 2000);
59 +}
60 +
61 +workb();
62 +```
63 +
64 +The `DEBUG` environment variable is then used to enable these based on space or
65 +comma-delimited names.
66 +
67 +Here are some examples:
68 +
69 +<img width="647" alt="screen shot 2017-08-08 at 12 53 04 pm" src="https://user-images.githubusercontent.com/71256/29091703-a6302cdc-7c38-11e7-8304-7c0b3bc600cd.png">
70 +<img width="647" alt="screen shot 2017-08-08 at 12 53 38 pm" src="https://user-images.githubusercontent.com/71256/29091700-a62a6888-7c38-11e7-800b-db911291ca2b.png">
71 +<img width="647" alt="screen shot 2017-08-08 at 12 53 25 pm" src="https://user-images.githubusercontent.com/71256/29091701-a62ea114-7c38-11e7-826a-2692bedca740.png">
72 +
73 +#### Windows command prompt notes
74 +
75 +##### CMD
76 +
77 +On Windows the environment variable is set using the `set` command.
78 +
79 +```cmd
80 +set DEBUG=*,-not_this
81 +```
82 +
83 +Example:
84 +
85 +```cmd
86 +set DEBUG=* & node app.js
87 +```
88 +
89 +##### PowerShell (VS Code default)
90 +
91 +PowerShell uses different syntax to set environment variables.
92 +
93 +```cmd
94 +$env:DEBUG = "*,-not_this"
95 +```
96 +
97 +Example:
98 +
99 +```cmd
100 +$env:DEBUG='app';node app.js
101 +```
102 +
103 +Then, run the program to be debugged as usual.
104 +
105 +npm script example:
106 +```js
107 + "windowsDebug": "@powershell -Command $env:DEBUG='*';node app.js",
108 +```
109 +
110 +## Namespace Colors
111 +
112 +Every debug instance has a color generated for it based on its namespace name.
113 +This helps when visually parsing the debug output to identify which debug instance
114 +a debug line belongs to.
115 +
116 +#### Node.js
117 +
118 +In Node.js, colors are enabled when stderr is a TTY. You also _should_ install
119 +the [`supports-color`](https://npmjs.org/supports-color) module alongside debug,
120 +otherwise debug will only use a small handful of basic colors.
121 +
122 +<img width="521" src="https://user-images.githubusercontent.com/71256/29092181-47f6a9e6-7c3a-11e7-9a14-1928d8a711cd.png">
123 +
124 +#### Web Browser
125 +
126 +Colors are also enabled on "Web Inspectors" that understand the `%c` formatting
127 +option. These are WebKit web inspectors, Firefox ([since version
128 +31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/))
129 +and the Firebug plugin for Firefox (any version).
130 +
131 +<img width="524" src="https://user-images.githubusercontent.com/71256/29092033-b65f9f2e-7c39-11e7-8e32-f6f0d8e865c1.png">
132 +
133 +
134 +## Millisecond diff
135 +
136 +When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls.
137 +
138 +<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
139 +
140 +When stdout is not a TTY, `Date#toISOString()` is used, making it more useful for logging the debug information as shown below:
141 +
142 +<img width="647" src="https://user-images.githubusercontent.com/71256/29091956-6bd78372-7c39-11e7-8c55-c948396d6edd.png">
143 +
144 +
145 +## Conventions
146 +
147 +If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". If you append a "*" to the end of your name, it will always be enabled regardless of the setting of the DEBUG environment variable. You can then use it for normal output as well as debug output.
148 +
149 +## Wildcards
150 +
151 +The `*` character may be used as a wildcard. Suppose for example your library has
152 +debuggers named "connect:bodyParser", "connect:compress", "connect:session",
153 +instead of listing all three with
154 +`DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do
155 +`DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.
156 +
157 +You can also exclude specific debuggers by prefixing them with a "-" character.
158 +For example, `DEBUG=*,-connect:*` would include all debuggers except those
159 +starting with "connect:".
160 +
161 +## Environment Variables
162 +
163 +When running through Node.js, you can set a few environment variables that will
164 +change the behavior of the debug logging:
165 +
166 +| Name | Purpose |
167 +|-----------|-------------------------------------------------|
168 +| `DEBUG` | Enables/disables specific debugging namespaces. |
169 +| `DEBUG_HIDE_DATE` | Hide date from debug output (non-TTY). |
170 +| `DEBUG_COLORS`| Whether or not to use colors in the debug output. |
171 +| `DEBUG_DEPTH` | Object inspection depth. |
172 +| `DEBUG_SHOW_HIDDEN` | Shows hidden properties on inspected objects. |
173 +
174 +
175 +__Note:__ The environment variables beginning with `DEBUG_` end up being
176 +converted into an Options object that gets used with `%o`/`%O` formatters.
177 +See the Node.js documentation for
178 +[`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options)
179 +for the complete list.
180 +
181 +## Formatters
182 +
183 +Debug uses [printf-style](https://wikipedia.org/wiki/Printf_format_string) formatting.
184 +Below are the officially supported formatters:
185 +
186 +| Formatter | Representation |
187 +|-----------|----------------|
188 +| `%O` | Pretty-print an Object on multiple lines. |
189 +| `%o` | Pretty-print an Object all on a single line. |
190 +| `%s` | String. |
191 +| `%d` | Number (both integer and float). |
192 +| `%j` | JSON. Replaced with the string '[Circular]' if the argument contains circular references. |
193 +| `%%` | Single percent sign ('%'). This does not consume an argument. |
194 +
195 +
196 +### Custom formatters
197 +
198 +You can add custom formatters by extending the `debug.formatters` object.
199 +For example, if you wanted to add support for rendering a Buffer as hex with
200 +`%h`, you could do something like:
201 +
202 +```js
203 +const createDebug = require('debug')
204 +createDebug.formatters.h = (v) => {
205 + return v.toString('hex')
206 +}
207 +
208 +// …elsewhere
209 +const debug = createDebug('foo')
210 +debug('this is hex: %h', new Buffer('hello world'))
211 +// foo this is hex: 68656c6c6f20776f726c6421 +0ms
212 +```
213 +
214 +
215 +## Browser Support
216 +
217 +You can build a browser-ready script using [browserify](https://github.com/substack/node-browserify),
218 +or just use the [browserify-as-a-service](https://wzrd.in/) [build](https://wzrd.in/standalone/debug@latest),
219 +if you don't want to build it yourself.
220 +
221 +Debug's enable state is currently persisted by `localStorage`.
222 +Consider the situation shown below where you have `worker:a` and `worker:b`,
223 +and wish to debug both. You can enable this using `localStorage.debug`:
224 +
225 +```js
226 +localStorage.debug = 'worker:*'
227 +```
228 +
229 +And then refresh the page.
230 +
231 +```js
232 +a = debug('worker:a');
233 +b = debug('worker:b');
234 +
235 +setInterval(function(){
236 + a('doing some work');
237 +}, 1000);
238 +
239 +setInterval(function(){
240 + b('doing some work');
241 +}, 1200);
242 +```
243 +
244 +
245 +## Output streams
246 +
247 + By default `debug` will log to stderr, however this can be configured per-namespace by overriding the `log` method:
248 +
249 +Example [_stdout.js_](./examples/node/stdout.js):
250 +
251 +```js
252 +var debug = require('debug');
253 +var error = debug('app:error');
254 +
255 +// by default stderr is used
256 +error('goes to stderr!');
257 +
258 +var log = debug('app:log');
259 +// set this namespace to log via console.log
260 +log.log = console.log.bind(console); // don't forget to bind to console!
261 +log('goes to stdout');
262 +error('still goes to stderr!');
263 +
264 +// set all output to go via console.info
265 +// overrides all per-namespace log settings
266 +debug.log = console.info.bind(console);
267 +error('now goes to stdout via console.info');
268 +log('still goes to stdout, but via console.info now');
269 +```
270 +
271 +## Extend
272 +You can simply extend debugger
273 +```js
274 +const log = require('debug')('auth');
275 +
276 +//creates new debug instance with extended namespace
277 +const logSign = log.extend('sign');
278 +const logLogin = log.extend('login');
279 +
280 +log('hello'); // auth hello
281 +logSign('hello'); //auth:sign hello
282 +logLogin('hello'); //auth:login hello
283 +```
284 +
285 +## Set dynamically
286 +
287 +You can also enable debug dynamically by calling the `enable()` method :
288 +
289 +```js
290 +let debug = require('debug');
291 +
292 +console.log(1, debug.enabled('test'));
293 +
294 +debug.enable('test');
295 +console.log(2, debug.enabled('test'));
296 +
297 +debug.disable();
298 +console.log(3, debug.enabled('test'));
299 +
300 +```
301 +
302 +print :
303 +```
304 +1 false
305 +2 true
306 +3 false
307 +```
308 +
309 +Usage :
310 +`enable(namespaces)`
311 +`namespaces` can include modes separated by a colon and wildcards.
312 +
313 +Note that calling `enable()` completely overrides previously set DEBUG variable :
314 +
315 +```
316 +$ DEBUG=foo node -e 'var dbg = require("debug"); dbg.enable("bar"); console.log(dbg.enabled("foo"))'
317 +=> false
318 +```
319 +
320 +`disable()`
321 +
322 +Will disable all namespaces. The functions returns the namespaces currently
323 +enabled (and skipped). This can be useful if you want to disable debugging
324 +temporarily without knowing what was enabled to begin with.
325 +
326 +For example:
327 +
328 +```js
329 +let debug = require('debug');
330 +debug.enable('foo:*,-foo:bar');
331 +let namespaces = debug.disable();
332 +debug.enable(namespaces);
333 +```
334 +
335 +Note: There is no guarantee that the string will be identical to the initial
336 +enable string, but semantically they will be identical.
337 +
338 +## Checking whether a debug target is enabled
339 +
340 +After you've created a debug instance, you can determine whether or not it is
341 +enabled by checking the `enabled` property:
342 +
343 +```javascript
344 +const debug = require('debug')('http');
345 +
346 +if (debug.enabled) {
347 + // do stuff...
348 +}
349 +```
350 +
351 +You can also manually toggle this property to force the debug instance to be
352 +enabled or disabled.
353 +
354 +
355 +## Authors
356 +
357 + - TJ Holowaychuk
358 + - Nathan Rajlich
359 + - Andrew Rhyne
360 +
361 +## Backers
362 +
363 +Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/debug#backer)]
364 +
365 +<a href="https://opencollective.com/debug/backer/0/website" target="_blank"><img src="https://opencollective.com/debug/backer/0/avatar.svg"></a>
366 +<a href="https://opencollective.com/debug/backer/1/website" target="_blank"><img src="https://opencollective.com/debug/backer/1/avatar.svg"></a>
367 +<a href="https://opencollective.com/debug/backer/2/website" target="_blank"><img src="https://opencollective.com/debug/backer/2/avatar.svg"></a>
368 +<a href="https://opencollective.com/debug/backer/3/website" target="_blank"><img src="https://opencollective.com/debug/backer/3/avatar.svg"></a>
369 +<a href="https://opencollective.com/debug/backer/4/website" target="_blank"><img src="https://opencollective.com/debug/backer/4/avatar.svg"></a>
370 +<a href="https://opencollective.com/debug/backer/5/website" target="_blank"><img src="https://opencollective.com/debug/backer/5/avatar.svg"></a>
371 +<a href="https://opencollective.com/debug/backer/6/website" target="_blank"><img src="https://opencollective.com/debug/backer/6/avatar.svg"></a>
372 +<a href="https://opencollective.com/debug/backer/7/website" target="_blank"><img src="https://opencollective.com/debug/backer/7/avatar.svg"></a>
373 +<a href="https://opencollective.com/debug/backer/8/website" target="_blank"><img src="https://opencollective.com/debug/backer/8/avatar.svg"></a>
374 +<a href="https://opencollective.com/debug/backer/9/website" target="_blank"><img src="https://opencollective.com/debug/backer/9/avatar.svg"></a>
375 +<a href="https://opencollective.com/debug/backer/10/website" target="_blank"><img src="https://opencollective.com/debug/backer/10/avatar.svg"></a>
376 +<a href="https://opencollective.com/debug/backer/11/website" target="_blank"><img src="https://opencollective.com/debug/backer/11/avatar.svg"></a>
377 +<a href="https://opencollective.com/debug/backer/12/website" target="_blank"><img src="https://opencollective.com/debug/backer/12/avatar.svg"></a>
378 +<a href="https://opencollective.com/debug/backer/13/website" target="_blank"><img src="https://opencollective.com/debug/backer/13/avatar.svg"></a>
379 +<a href="https://opencollective.com/debug/backer/14/website" target="_blank"><img src="https://opencollective.com/debug/backer/14/avatar.svg"></a>
380 +<a href="https://opencollective.com/debug/backer/15/website" target="_blank"><img src="https://opencollective.com/debug/backer/15/avatar.svg"></a>
381 +<a href="https://opencollective.com/debug/backer/16/website" target="_blank"><img src="https://opencollective.com/debug/backer/16/avatar.svg"></a>
382 +<a href="https://opencollective.com/debug/backer/17/website" target="_blank"><img src="https://opencollective.com/debug/backer/17/avatar.svg"></a>
383 +<a href="https://opencollective.com/debug/backer/18/website" target="_blank"><img src="https://opencollective.com/debug/backer/18/avatar.svg"></a>
384 +<a href="https://opencollective.com/debug/backer/19/website" target="_blank"><img src="https://opencollective.com/debug/backer/19/avatar.svg"></a>
385 +<a href="https://opencollective.com/debug/backer/20/website" target="_blank"><img src="https://opencollective.com/debug/backer/20/avatar.svg"></a>
386 +<a href="https://opencollective.com/debug/backer/21/website" target="_blank"><img src="https://opencollective.com/debug/backer/21/avatar.svg"></a>
387 +<a href="https://opencollective.com/debug/backer/22/website" target="_blank"><img src="https://opencollective.com/debug/backer/22/avatar.svg"></a>
388 +<a href="https://opencollective.com/debug/backer/23/website" target="_blank"><img src="https://opencollective.com/debug/backer/23/avatar.svg"></a>
389 +<a href="https://opencollective.com/debug/backer/24/website" target="_blank"><img src="https://opencollective.com/debug/backer/24/avatar.svg"></a>
390 +<a href="https://opencollective.com/debug/backer/25/website" target="_blank"><img src="https://opencollective.com/debug/backer/25/avatar.svg"></a>
391 +<a href="https://opencollective.com/debug/backer/26/website" target="_blank"><img src="https://opencollective.com/debug/backer/26/avatar.svg"></a>
392 +<a href="https://opencollective.com/debug/backer/27/website" target="_blank"><img src="https://opencollective.com/debug/backer/27/avatar.svg"></a>
393 +<a href="https://opencollective.com/debug/backer/28/website" target="_blank"><img src="https://opencollective.com/debug/backer/28/avatar.svg"></a>
394 +<a href="https://opencollective.com/debug/backer/29/website" target="_blank"><img src="https://opencollective.com/debug/backer/29/avatar.svg"></a>
395 +
396 +
397 +## Sponsors
398 +
399 +Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/debug#sponsor)]
400 +
401 +<a href="https://opencollective.com/debug/sponsor/0/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/0/avatar.svg"></a>
402 +<a href="https://opencollective.com/debug/sponsor/1/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/1/avatar.svg"></a>
403 +<a href="https://opencollective.com/debug/sponsor/2/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/2/avatar.svg"></a>
404 +<a href="https://opencollective.com/debug/sponsor/3/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/3/avatar.svg"></a>
405 +<a href="https://opencollective.com/debug/sponsor/4/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/4/avatar.svg"></a>
406 +<a href="https://opencollective.com/debug/sponsor/5/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/5/avatar.svg"></a>
407 +<a href="https://opencollective.com/debug/sponsor/6/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/6/avatar.svg"></a>
408 +<a href="https://opencollective.com/debug/sponsor/7/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/7/avatar.svg"></a>
409 +<a href="https://opencollective.com/debug/sponsor/8/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/8/avatar.svg"></a>
410 +<a href="https://opencollective.com/debug/sponsor/9/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/9/avatar.svg"></a>
411 +<a href="https://opencollective.com/debug/sponsor/10/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/10/avatar.svg"></a>
412 +<a href="https://opencollective.com/debug/sponsor/11/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/11/avatar.svg"></a>
413 +<a href="https://opencollective.com/debug/sponsor/12/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/12/avatar.svg"></a>
414 +<a href="https://opencollective.com/debug/sponsor/13/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/13/avatar.svg"></a>
415 +<a href="https://opencollective.com/debug/sponsor/14/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/14/avatar.svg"></a>
416 +<a href="https://opencollective.com/debug/sponsor/15/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/15/avatar.svg"></a>
417 +<a href="https://opencollective.com/debug/sponsor/16/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/16/avatar.svg"></a>
418 +<a href="https://opencollective.com/debug/sponsor/17/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/17/avatar.svg"></a>
419 +<a href="https://opencollective.com/debug/sponsor/18/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/18/avatar.svg"></a>
420 +<a href="https://opencollective.com/debug/sponsor/19/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/19/avatar.svg"></a>
421 +<a href="https://opencollective.com/debug/sponsor/20/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/20/avatar.svg"></a>
422 +<a href="https://opencollective.com/debug/sponsor/21/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/21/avatar.svg"></a>
423 +<a href="https://opencollective.com/debug/sponsor/22/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/22/avatar.svg"></a>
424 +<a href="https://opencollective.com/debug/sponsor/23/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/23/avatar.svg"></a>
425 +<a href="https://opencollective.com/debug/sponsor/24/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/24/avatar.svg"></a>
426 +<a href="https://opencollective.com/debug/sponsor/25/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/25/avatar.svg"></a>
427 +<a href="https://opencollective.com/debug/sponsor/26/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/26/avatar.svg"></a>
428 +<a href="https://opencollective.com/debug/sponsor/27/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/27/avatar.svg"></a>
429 +<a href="https://opencollective.com/debug/sponsor/28/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/28/avatar.svg"></a>
430 +<a href="https://opencollective.com/debug/sponsor/29/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/29/avatar.svg"></a>
431 +
432 +## License
433 +
434 +(The MIT License)
435 +
436 +Copyright (c) 2014-2017 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
437 +
438 +Permission is hereby granted, free of charge, to any person obtaining
439 +a copy of this software and associated documentation files (the
440 +'Software'), to deal in the Software without restriction, including
441 +without limitation the rights to use, copy, modify, merge, publish,
442 +distribute, sublicense, and/or sell copies of the Software, and to
443 +permit persons to whom the Software is furnished to do so, subject to
444 +the following conditions:
445 +
446 +The above copyright notice and this permission notice shall be
447 +included in all copies or substantial portions of the Software.
448 +
449 +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
450 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
451 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
452 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
453 +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
454 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
455 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1 +"use strict";
2 +
3 +function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
4 +
5 +function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
6 +
7 +function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
8 +
9 +function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
10 +
11 +function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
12 +
13 +(function (f) {
14 + if ((typeof exports === "undefined" ? "undefined" : _typeof(exports)) === "object" && typeof module !== "undefined") {
15 + module.exports = f();
16 + } else if (typeof define === "function" && define.amd) {
17 + define([], f);
18 + } else {
19 + var g;
20 +
21 + if (typeof window !== "undefined") {
22 + g = window;
23 + } else if (typeof global !== "undefined") {
24 + g = global;
25 + } else if (typeof self !== "undefined") {
26 + g = self;
27 + } else {
28 + g = this;
29 + }
30 +
31 + g.debug = f();
32 + }
33 +})(function () {
34 + var define, module, exports;
35 + return function () {
36 + function r(e, n, t) {
37 + function o(i, f) {
38 + if (!n[i]) {
39 + if (!e[i]) {
40 + var c = "function" == typeof require && require;
41 + if (!f && c) return c(i, !0);
42 + if (u) return u(i, !0);
43 + var a = new Error("Cannot find module '" + i + "'");
44 + throw a.code = "MODULE_NOT_FOUND", a;
45 + }
46 +
47 + var p = n[i] = {
48 + exports: {}
49 + };
50 + e[i][0].call(p.exports, function (r) {
51 + var n = e[i][1][r];
52 + return o(n || r);
53 + }, p, p.exports, r, e, n, t);
54 + }
55 +
56 + return n[i].exports;
57 + }
58 +
59 + for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) {
60 + o(t[i]);
61 + }
62 +
63 + return o;
64 + }
65 +
66 + return r;
67 + }()({
68 + 1: [function (require, module, exports) {
69 + /**
70 + * Helpers.
71 + */
72 + var s = 1000;
73 + var m = s * 60;
74 + var h = m * 60;
75 + var d = h * 24;
76 + var w = d * 7;
77 + var y = d * 365.25;
78 + /**
79 + * Parse or format the given `val`.
80 + *
81 + * Options:
82 + *
83 + * - `long` verbose formatting [false]
84 + *
85 + * @param {String|Number} val
86 + * @param {Object} [options]
87 + * @throws {Error} throw an error if val is not a non-empty string or a number
88 + * @return {String|Number}
89 + * @api public
90 + */
91 +
92 + module.exports = function (val, options) {
93 + options = options || {};
94 +
95 + var type = _typeof(val);
96 +
97 + if (type === 'string' && val.length > 0) {
98 + return parse(val);
99 + } else if (type === 'number' && isNaN(val) === false) {
100 + return options.long ? fmtLong(val) : fmtShort(val);
101 + }
102 +
103 + throw new Error('val is not a non-empty string or a valid number. val=' + JSON.stringify(val));
104 + };
105 + /**
106 + * Parse the given `str` and return milliseconds.
107 + *
108 + * @param {String} str
109 + * @return {Number}
110 + * @api private
111 + */
112 +
113 +
114 + function parse(str) {
115 + str = String(str);
116 +
117 + if (str.length > 100) {
118 + return;
119 + }
120 +
121 + var match = /^((?:\d+)?\-?\d?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(str);
122 +
123 + if (!match) {
124 + return;
125 + }
126 +
127 + var n = parseFloat(match[1]);
128 + var type = (match[2] || 'ms').toLowerCase();
129 +
130 + switch (type) {
131 + case 'years':
132 + case 'year':
133 + case 'yrs':
134 + case 'yr':
135 + case 'y':
136 + return n * y;
137 +
138 + case 'weeks':
139 + case 'week':
140 + case 'w':
141 + return n * w;
142 +
143 + case 'days':
144 + case 'day':
145 + case 'd':
146 + return n * d;
147 +
148 + case 'hours':
149 + case 'hour':
150 + case 'hrs':
151 + case 'hr':
152 + case 'h':
153 + return n * h;
154 +
155 + case 'minutes':
156 + case 'minute':
157 + case 'mins':
158 + case 'min':
159 + case 'm':
160 + return n * m;
161 +
162 + case 'seconds':
163 + case 'second':
164 + case 'secs':
165 + case 'sec':
166 + case 's':
167 + return n * s;
168 +
169 + case 'milliseconds':
170 + case 'millisecond':
171 + case 'msecs':
172 + case 'msec':
173 + case 'ms':
174 + return n;
175 +
176 + default:
177 + return undefined;
178 + }
179 + }
180 + /**
181 + * Short format for `ms`.
182 + *
183 + * @param {Number} ms
184 + * @return {String}
185 + * @api private
186 + */
187 +
188 +
189 + function fmtShort(ms) {
190 + var msAbs = Math.abs(ms);
191 +
192 + if (msAbs >= d) {
193 + return Math.round(ms / d) + 'd';
194 + }
195 +
196 + if (msAbs >= h) {
197 + return Math.round(ms / h) + 'h';
198 + }
199 +
200 + if (msAbs >= m) {
201 + return Math.round(ms / m) + 'm';
202 + }
203 +
204 + if (msAbs >= s) {
205 + return Math.round(ms / s) + 's';
206 + }
207 +
208 + return ms + 'ms';
209 + }
210 + /**
211 + * Long format for `ms`.
212 + *
213 + * @param {Number} ms
214 + * @return {String}
215 + * @api private
216 + */
217 +
218 +
219 + function fmtLong(ms) {
220 + var msAbs = Math.abs(ms);
221 +
222 + if (msAbs >= d) {
223 + return plural(ms, msAbs, d, 'day');
224 + }
225 +
226 + if (msAbs >= h) {
227 + return plural(ms, msAbs, h, 'hour');
228 + }
229 +
230 + if (msAbs >= m) {
231 + return plural(ms, msAbs, m, 'minute');
232 + }
233 +
234 + if (msAbs >= s) {
235 + return plural(ms, msAbs, s, 'second');
236 + }
237 +
238 + return ms + ' ms';
239 + }
240 + /**
241 + * Pluralization helper.
242 + */
243 +
244 +
245 + function plural(ms, msAbs, n, name) {
246 + var isPlural = msAbs >= n * 1.5;
247 + return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
248 + }
249 + }, {}],
250 + 2: [function (require, module, exports) {
251 + // shim for using process in browser
252 + var process = module.exports = {}; // cached from whatever global is present so that test runners that stub it
253 + // don't break things. But we need to wrap it in a try catch in case it is
254 + // wrapped in strict mode code which doesn't define any globals. It's inside a
255 + // function because try/catches deoptimize in certain engines.
256 +
257 + var cachedSetTimeout;
258 + var cachedClearTimeout;
259 +
260 + function defaultSetTimout() {
261 + throw new Error('setTimeout has not been defined');
262 + }
263 +
264 + function defaultClearTimeout() {
265 + throw new Error('clearTimeout has not been defined');
266 + }
267 +
268 + (function () {
269 + try {
270 + if (typeof setTimeout === 'function') {
271 + cachedSetTimeout = setTimeout;
272 + } else {
273 + cachedSetTimeout = defaultSetTimout;
274 + }
275 + } catch (e) {
276 + cachedSetTimeout = defaultSetTimout;
277 + }
278 +
279 + try {
280 + if (typeof clearTimeout === 'function') {
281 + cachedClearTimeout = clearTimeout;
282 + } else {
283 + cachedClearTimeout = defaultClearTimeout;
284 + }
285 + } catch (e) {
286 + cachedClearTimeout = defaultClearTimeout;
287 + }
288 + })();
289 +
290 + function runTimeout(fun) {
291 + if (cachedSetTimeout === setTimeout) {
292 + //normal enviroments in sane situations
293 + return setTimeout(fun, 0);
294 + } // if setTimeout wasn't available but was latter defined
295 +
296 +
297 + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
298 + cachedSetTimeout = setTimeout;
299 + return setTimeout(fun, 0);
300 + }
301 +
302 + try {
303 + // when when somebody has screwed with setTimeout but no I.E. maddness
304 + return cachedSetTimeout(fun, 0);
305 + } catch (e) {
306 + try {
307 + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
308 + return cachedSetTimeout.call(null, fun, 0);
309 + } catch (e) {
310 + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
311 + return cachedSetTimeout.call(this, fun, 0);
312 + }
313 + }
314 + }
315 +
316 + function runClearTimeout(marker) {
317 + if (cachedClearTimeout === clearTimeout) {
318 + //normal enviroments in sane situations
319 + return clearTimeout(marker);
320 + } // if clearTimeout wasn't available but was latter defined
321 +
322 +
323 + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
324 + cachedClearTimeout = clearTimeout;
325 + return clearTimeout(marker);
326 + }
327 +
328 + try {
329 + // when when somebody has screwed with setTimeout but no I.E. maddness
330 + return cachedClearTimeout(marker);
331 + } catch (e) {
332 + try {
333 + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
334 + return cachedClearTimeout.call(null, marker);
335 + } catch (e) {
336 + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
337 + // Some versions of I.E. have different rules for clearTimeout vs setTimeout
338 + return cachedClearTimeout.call(this, marker);
339 + }
340 + }
341 + }
342 +
343 + var queue = [];
344 + var draining = false;
345 + var currentQueue;
346 + var queueIndex = -1;
347 +
348 + function cleanUpNextTick() {
349 + if (!draining || !currentQueue) {
350 + return;
351 + }
352 +
353 + draining = false;
354 +
355 + if (currentQueue.length) {
356 + queue = currentQueue.concat(queue);
357 + } else {
358 + queueIndex = -1;
359 + }
360 +
361 + if (queue.length) {
362 + drainQueue();
363 + }
364 + }
365 +
366 + function drainQueue() {
367 + if (draining) {
368 + return;
369 + }
370 +
371 + var timeout = runTimeout(cleanUpNextTick);
372 + draining = true;
373 + var len = queue.length;
374 +
375 + while (len) {
376 + currentQueue = queue;
377 + queue = [];
378 +
379 + while (++queueIndex < len) {
380 + if (currentQueue) {
381 + currentQueue[queueIndex].run();
382 + }
383 + }
384 +
385 + queueIndex = -1;
386 + len = queue.length;
387 + }
388 +
389 + currentQueue = null;
390 + draining = false;
391 + runClearTimeout(timeout);
392 + }
393 +
394 + process.nextTick = function (fun) {
395 + var args = new Array(arguments.length - 1);
396 +
397 + if (arguments.length > 1) {
398 + for (var i = 1; i < arguments.length; i++) {
399 + args[i - 1] = arguments[i];
400 + }
401 + }
402 +
403 + queue.push(new Item(fun, args));
404 +
405 + if (queue.length === 1 && !draining) {
406 + runTimeout(drainQueue);
407 + }
408 + }; // v8 likes predictible objects
409 +
410 +
411 + function Item(fun, array) {
412 + this.fun = fun;
413 + this.array = array;
414 + }
415 +
416 + Item.prototype.run = function () {
417 + this.fun.apply(null, this.array);
418 + };
419 +
420 + process.title = 'browser';
421 + process.browser = true;
422 + process.env = {};
423 + process.argv = [];
424 + process.version = ''; // empty string to avoid regexp issues
425 +
426 + process.versions = {};
427 +
428 + function noop() {}
429 +
430 + process.on = noop;
431 + process.addListener = noop;
432 + process.once = noop;
433 + process.off = noop;
434 + process.removeListener = noop;
435 + process.removeAllListeners = noop;
436 + process.emit = noop;
437 + process.prependListener = noop;
438 + process.prependOnceListener = noop;
439 +
440 + process.listeners = function (name) {
441 + return [];
442 + };
443 +
444 + process.binding = function (name) {
445 + throw new Error('process.binding is not supported');
446 + };
447 +
448 + process.cwd = function () {
449 + return '/';
450 + };
451 +
452 + process.chdir = function (dir) {
453 + throw new Error('process.chdir is not supported');
454 + };
455 +
456 + process.umask = function () {
457 + return 0;
458 + };
459 + }, {}],
460 + 3: [function (require, module, exports) {
461 + /**
462 + * This is the common logic for both the Node.js and web browser
463 + * implementations of `debug()`.
464 + */
465 + function setup(env) {
466 + createDebug.debug = createDebug;
467 + createDebug.default = createDebug;
468 + createDebug.coerce = coerce;
469 + createDebug.disable = disable;
470 + createDebug.enable = enable;
471 + createDebug.enabled = enabled;
472 + createDebug.humanize = require('ms');
473 + Object.keys(env).forEach(function (key) {
474 + createDebug[key] = env[key];
475 + });
476 + /**
477 + * Active `debug` instances.
478 + */
479 +
480 + createDebug.instances = [];
481 + /**
482 + * The currently active debug mode names, and names to skip.
483 + */
484 +
485 + createDebug.names = [];
486 + createDebug.skips = [];
487 + /**
488 + * Map of special "%n" handling functions, for the debug "format" argument.
489 + *
490 + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
491 + */
492 +
493 + createDebug.formatters = {};
494 + /**
495 + * Selects a color for a debug namespace
496 + * @param {String} namespace The namespace string for the for the debug instance to be colored
497 + * @return {Number|String} An ANSI color code for the given namespace
498 + * @api private
499 + */
500 +
501 + function selectColor(namespace) {
502 + var hash = 0;
503 +
504 + for (var i = 0; i < namespace.length; i++) {
505 + hash = (hash << 5) - hash + namespace.charCodeAt(i);
506 + hash |= 0; // Convert to 32bit integer
507 + }
508 +
509 + return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
510 + }
511 +
512 + createDebug.selectColor = selectColor;
513 + /**
514 + * Create a debugger with the given `namespace`.
515 + *
516 + * @param {String} namespace
517 + * @return {Function}
518 + * @api public
519 + */
520 +
521 + function createDebug(namespace) {
522 + var prevTime;
523 +
524 + function debug() {
525 + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
526 + args[_key] = arguments[_key];
527 + }
528 +
529 + // Disabled?
530 + if (!debug.enabled) {
531 + return;
532 + }
533 +
534 + var self = debug; // Set `diff` timestamp
535 +
536 + var curr = Number(new Date());
537 + var ms = curr - (prevTime || curr);
538 + self.diff = ms;
539 + self.prev = prevTime;
540 + self.curr = curr;
541 + prevTime = curr;
542 + args[0] = createDebug.coerce(args[0]);
543 +
544 + if (typeof args[0] !== 'string') {
545 + // Anything else let's inspect with %O
546 + args.unshift('%O');
547 + } // Apply any `formatters` transformations
548 +
549 +
550 + var index = 0;
551 + args[0] = args[0].replace(/%([a-zA-Z%])/g, function (match, format) {
552 + // If we encounter an escaped % then don't increase the array index
553 + if (match === '%%') {
554 + return match;
555 + }
556 +
557 + index++;
558 + var formatter = createDebug.formatters[format];
559 +
560 + if (typeof formatter === 'function') {
561 + var val = args[index];
562 + match = formatter.call(self, val); // Now we need to remove `args[index]` since it's inlined in the `format`
563 +
564 + args.splice(index, 1);
565 + index--;
566 + }
567 +
568 + return match;
569 + }); // Apply env-specific formatting (colors, etc.)
570 +
571 + createDebug.formatArgs.call(self, args);
572 + var logFn = self.log || createDebug.log;
573 + logFn.apply(self, args);
574 + }
575 +
576 + debug.namespace = namespace;
577 + debug.enabled = createDebug.enabled(namespace);
578 + debug.useColors = createDebug.useColors();
579 + debug.color = selectColor(namespace);
580 + debug.destroy = destroy;
581 + debug.extend = extend; // Debug.formatArgs = formatArgs;
582 + // debug.rawLog = rawLog;
583 + // env-specific initialization logic for debug instances
584 +
585 + if (typeof createDebug.init === 'function') {
586 + createDebug.init(debug);
587 + }
588 +
589 + createDebug.instances.push(debug);
590 + return debug;
591 + }
592 +
593 + function destroy() {
594 + var index = createDebug.instances.indexOf(this);
595 +
596 + if (index !== -1) {
597 + createDebug.instances.splice(index, 1);
598 + return true;
599 + }
600 +
601 + return false;
602 + }
603 +
604 + function extend(namespace, delimiter) {
605 + var newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
606 + newDebug.log = this.log;
607 + return newDebug;
608 + }
609 + /**
610 + * Enables a debug mode by namespaces. This can include modes
611 + * separated by a colon and wildcards.
612 + *
613 + * @param {String} namespaces
614 + * @api public
615 + */
616 +
617 +
618 + function enable(namespaces) {
619 + createDebug.save(namespaces);
620 + createDebug.names = [];
621 + createDebug.skips = [];
622 + var i;
623 + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
624 + var len = split.length;
625 +
626 + for (i = 0; i < len; i++) {
627 + if (!split[i]) {
628 + // ignore empty strings
629 + continue;
630 + }
631 +
632 + namespaces = split[i].replace(/\*/g, '.*?');
633 +
634 + if (namespaces[0] === '-') {
635 + createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
636 + } else {
637 + createDebug.names.push(new RegExp('^' + namespaces + '$'));
638 + }
639 + }
640 +
641 + for (i = 0; i < createDebug.instances.length; i++) {
642 + var instance = createDebug.instances[i];
643 + instance.enabled = createDebug.enabled(instance.namespace);
644 + }
645 + }
646 + /**
647 + * Disable debug output.
648 + *
649 + * @return {String} namespaces
650 + * @api public
651 + */
652 +
653 +
654 + function disable() {
655 + var namespaces = [].concat(_toConsumableArray(createDebug.names.map(toNamespace)), _toConsumableArray(createDebug.skips.map(toNamespace).map(function (namespace) {
656 + return '-' + namespace;
657 + }))).join(',');
658 + createDebug.enable('');
659 + return namespaces;
660 + }
661 + /**
662 + * Returns true if the given mode name is enabled, false otherwise.
663 + *
664 + * @param {String} name
665 + * @return {Boolean}
666 + * @api public
667 + */
668 +
669 +
670 + function enabled(name) {
671 + if (name[name.length - 1] === '*') {
672 + return true;
673 + }
674 +
675 + var i;
676 + var len;
677 +
678 + for (i = 0, len = createDebug.skips.length; i < len; i++) {
679 + if (createDebug.skips[i].test(name)) {
680 + return false;
681 + }
682 + }
683 +
684 + for (i = 0, len = createDebug.names.length; i < len; i++) {
685 + if (createDebug.names[i].test(name)) {
686 + return true;
687 + }
688 + }
689 +
690 + return false;
691 + }
692 + /**
693 + * Convert regexp to namespace
694 + *
695 + * @param {RegExp} regxep
696 + * @return {String} namespace
697 + * @api private
698 + */
699 +
700 +
701 + function toNamespace(regexp) {
702 + return regexp.toString().substring(2, regexp.toString().length - 2).replace(/\.\*\?$/, '*');
703 + }
704 + /**
705 + * Coerce `val`.
706 + *
707 + * @param {Mixed} val
708 + * @return {Mixed}
709 + * @api private
710 + */
711 +
712 +
713 + function coerce(val) {
714 + if (val instanceof Error) {
715 + return val.stack || val.message;
716 + }
717 +
718 + return val;
719 + }
720 +
721 + createDebug.enable(createDebug.load());
722 + return createDebug;
723 + }
724 +
725 + module.exports = setup;
726 + }, {
727 + "ms": 1
728 + }],
729 + 4: [function (require, module, exports) {
730 + (function (process) {
731 + /* eslint-env browser */
732 +
733 + /**
734 + * This is the web browser implementation of `debug()`.
735 + */
736 + exports.log = log;
737 + exports.formatArgs = formatArgs;
738 + exports.save = save;
739 + exports.load = load;
740 + exports.useColors = useColors;
741 + exports.storage = localstorage();
742 + /**
743 + * Colors.
744 + */
745 +
746 + exports.colors = ['#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC', '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF', '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC', '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033', '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366', '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933', '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC', '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33'];
747 + /**
748 + * Currently only WebKit-based Web Inspectors, Firefox >= v31,
749 + * and the Firebug extension (any Firefox version) are known
750 + * to support "%c" CSS customizations.
751 + *
752 + * TODO: add a `localStorage` variable to explicitly enable/disable colors
753 + */
754 + // eslint-disable-next-line complexity
755 +
756 + function useColors() {
757 + // NB: In an Electron preload script, document will be defined but not fully
758 + // initialized. Since we know we're in Chrome, we'll just detect this case
759 + // explicitly
760 + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
761 + return true;
762 + } // Internet Explorer and Edge do not support colors.
763 +
764 +
765 + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
766 + return false;
767 + } // Is webkit? http://stackoverflow.com/a/16459606/376773
768 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
769 +
770 +
771 + return typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
772 + typeof window !== 'undefined' && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
773 + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
774 + typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
775 + typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
776 + }
777 + /**
778 + * Colorize log arguments if enabled.
779 + *
780 + * @api public
781 + */
782 +
783 +
784 + function formatArgs(args) {
785 + args[0] = (this.useColors ? '%c' : '') + this.namespace + (this.useColors ? ' %c' : ' ') + args[0] + (this.useColors ? '%c ' : ' ') + '+' + module.exports.humanize(this.diff);
786 +
787 + if (!this.useColors) {
788 + return;
789 + }
790 +
791 + var c = 'color: ' + this.color;
792 + args.splice(1, 0, c, 'color: inherit'); // The final "%c" is somewhat tricky, because there could be other
793 + // arguments passed either before or after the %c, so we need to
794 + // figure out the correct index to insert the CSS into
795 +
796 + var index = 0;
797 + var lastC = 0;
798 + args[0].replace(/%[a-zA-Z%]/g, function (match) {
799 + if (match === '%%') {
800 + return;
801 + }
802 +
803 + index++;
804 +
805 + if (match === '%c') {
806 + // We only are interested in the *last* %c
807 + // (the user may have provided their own)
808 + lastC = index;
809 + }
810 + });
811 + args.splice(lastC, 0, c);
812 + }
813 + /**
814 + * Invokes `console.log()` when available.
815 + * No-op when `console.log` is not a "function".
816 + *
817 + * @api public
818 + */
819 +
820 +
821 + function log() {
822 + var _console;
823 +
824 + // This hackery is required for IE8/9, where
825 + // the `console.log` function doesn't have 'apply'
826 + return (typeof console === "undefined" ? "undefined" : _typeof(console)) === 'object' && console.log && (_console = console).log.apply(_console, arguments);
827 + }
828 + /**
829 + * Save `namespaces`.
830 + *
831 + * @param {String} namespaces
832 + * @api private
833 + */
834 +
835 +
836 + function save(namespaces) {
837 + try {
838 + if (namespaces) {
839 + exports.storage.setItem('debug', namespaces);
840 + } else {
841 + exports.storage.removeItem('debug');
842 + }
843 + } catch (error) {// Swallow
844 + // XXX (@Qix-) should we be logging these?
845 + }
846 + }
847 + /**
848 + * Load `namespaces`.
849 + *
850 + * @return {String} returns the previously persisted debug modes
851 + * @api private
852 + */
853 +
854 +
855 + function load() {
856 + var r;
857 +
858 + try {
859 + r = exports.storage.getItem('debug');
860 + } catch (error) {} // Swallow
861 + // XXX (@Qix-) should we be logging these?
862 + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG
863 +
864 +
865 + if (!r && typeof process !== 'undefined' && 'env' in process) {
866 + r = process.env.DEBUG;
867 + }
868 +
869 + return r;
870 + }
871 + /**
872 + * Localstorage attempts to return the localstorage.
873 + *
874 + * This is necessary because safari throws
875 + * when a user disables cookies/localstorage
876 + * and you attempt to access it.
877 + *
878 + * @return {LocalStorage}
879 + * @api private
880 + */
881 +
882 +
883 + function localstorage() {
884 + try {
885 + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
886 + // The Browser also has localStorage in the global context.
887 + return localStorage;
888 + } catch (error) {// Swallow
889 + // XXX (@Qix-) should we be logging these?
890 + }
891 + }
892 +
893 + module.exports = require('./common')(exports);
894 + var formatters = module.exports.formatters;
895 + /**
896 + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
897 + */
898 +
899 + formatters.j = function (v) {
900 + try {
901 + return JSON.stringify(v);
902 + } catch (error) {
903 + return '[UnexpectedJSONParseError]: ' + error.message;
904 + }
905 + };
906 + }).call(this, require('_process'));
907 + }, {
908 + "./common": 3,
909 + "_process": 2
910 + }]
911 + }, {}, [4])(4);
912 +});
1 +{
2 + "_from": "debug@^4.1.1",
3 + "_id": "debug@4.1.1",
4 + "_inBundle": false,
5 + "_integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
6 + "_location": "/messaging-api-line/debug",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "debug@^4.1.1",
12 + "name": "debug",
13 + "escapedName": "debug",
14 + "rawSpec": "^4.1.1",
15 + "saveSpec": null,
16 + "fetchSpec": "^4.1.1"
17 + },
18 + "_requiredBy": [
19 + "/messaging-api-line"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
22 + "_shasum": "3b72260255109c6b589cee050f1d516139664791",
23 + "_spec": "debug@^4.1.1",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\messaging-api-line",
25 + "author": {
26 + "name": "TJ Holowaychuk",
27 + "email": "tj@vision-media.ca"
28 + },
29 + "browser": "./src/browser.js",
30 + "bugs": {
31 + "url": "https://github.com/visionmedia/debug/issues"
32 + },
33 + "bundleDependencies": false,
34 + "contributors": [
35 + {
36 + "name": "Nathan Rajlich",
37 + "email": "nathan@tootallnate.net",
38 + "url": "http://n8.io"
39 + },
40 + {
41 + "name": "Andrew Rhyne",
42 + "email": "rhyneandrew@gmail.com"
43 + }
44 + ],
45 + "dependencies": {
46 + "ms": "^2.1.1"
47 + },
48 + "deprecated": false,
49 + "description": "small debugging utility",
50 + "devDependencies": {
51 + "@babel/cli": "^7.0.0",
52 + "@babel/core": "^7.0.0",
53 + "@babel/preset-env": "^7.0.0",
54 + "browserify": "14.4.0",
55 + "chai": "^3.5.0",
56 + "concurrently": "^3.1.0",
57 + "coveralls": "^3.0.2",
58 + "istanbul": "^0.4.5",
59 + "karma": "^3.0.0",
60 + "karma-chai": "^0.1.0",
61 + "karma-mocha": "^1.3.0",
62 + "karma-phantomjs-launcher": "^1.0.2",
63 + "mocha": "^5.2.0",
64 + "mocha-lcov-reporter": "^1.2.0",
65 + "rimraf": "^2.5.4",
66 + "xo": "^0.23.0"
67 + },
68 + "files": [
69 + "src",
70 + "dist/debug.js",
71 + "LICENSE",
72 + "README.md"
73 + ],
74 + "homepage": "https://github.com/visionmedia/debug#readme",
75 + "keywords": [
76 + "debug",
77 + "log",
78 + "debugger"
79 + ],
80 + "license": "MIT",
81 + "main": "./src/index.js",
82 + "name": "debug",
83 + "repository": {
84 + "type": "git",
85 + "url": "git://github.com/visionmedia/debug.git"
86 + },
87 + "scripts": {
88 + "build": "npm run build:debug && npm run build:test",
89 + "build:debug": "babel -o dist/debug.js dist/debug.es6.js > dist/debug.js",
90 + "build:test": "babel -d dist test.js",
91 + "clean": "rimraf dist coverage",
92 + "lint": "xo",
93 + "prebuild:debug": "mkdir -p dist && browserify --standalone debug -o dist/debug.es6.js .",
94 + "pretest:browser": "npm run build",
95 + "test": "npm run test:node && npm run test:browser",
96 + "test:browser": "karma start --single-run",
97 + "test:coverage": "cat ./coverage/lcov.info | coveralls",
98 + "test:node": "istanbul cover _mocha -- test.js"
99 + },
100 + "unpkg": "./dist/debug.js",
101 + "version": "4.1.1"
102 +}
1 +/* eslint-env browser */
2 +
3 +/**
4 + * This is the web browser implementation of `debug()`.
5 + */
6 +
7 +exports.log = log;
8 +exports.formatArgs = formatArgs;
9 +exports.save = save;
10 +exports.load = load;
11 +exports.useColors = useColors;
12 +exports.storage = localstorage();
13 +
14 +/**
15 + * Colors.
16 + */
17 +
18 +exports.colors = [
19 + '#0000CC',
20 + '#0000FF',
21 + '#0033CC',
22 + '#0033FF',
23 + '#0066CC',
24 + '#0066FF',
25 + '#0099CC',
26 + '#0099FF',
27 + '#00CC00',
28 + '#00CC33',
29 + '#00CC66',
30 + '#00CC99',
31 + '#00CCCC',
32 + '#00CCFF',
33 + '#3300CC',
34 + '#3300FF',
35 + '#3333CC',
36 + '#3333FF',
37 + '#3366CC',
38 + '#3366FF',
39 + '#3399CC',
40 + '#3399FF',
41 + '#33CC00',
42 + '#33CC33',
43 + '#33CC66',
44 + '#33CC99',
45 + '#33CCCC',
46 + '#33CCFF',
47 + '#6600CC',
48 + '#6600FF',
49 + '#6633CC',
50 + '#6633FF',
51 + '#66CC00',
52 + '#66CC33',
53 + '#9900CC',
54 + '#9900FF',
55 + '#9933CC',
56 + '#9933FF',
57 + '#99CC00',
58 + '#99CC33',
59 + '#CC0000',
60 + '#CC0033',
61 + '#CC0066',
62 + '#CC0099',
63 + '#CC00CC',
64 + '#CC00FF',
65 + '#CC3300',
66 + '#CC3333',
67 + '#CC3366',
68 + '#CC3399',
69 + '#CC33CC',
70 + '#CC33FF',
71 + '#CC6600',
72 + '#CC6633',
73 + '#CC9900',
74 + '#CC9933',
75 + '#CCCC00',
76 + '#CCCC33',
77 + '#FF0000',
78 + '#FF0033',
79 + '#FF0066',
80 + '#FF0099',
81 + '#FF00CC',
82 + '#FF00FF',
83 + '#FF3300',
84 + '#FF3333',
85 + '#FF3366',
86 + '#FF3399',
87 + '#FF33CC',
88 + '#FF33FF',
89 + '#FF6600',
90 + '#FF6633',
91 + '#FF9900',
92 + '#FF9933',
93 + '#FFCC00',
94 + '#FFCC33'
95 +];
96 +
97 +/**
98 + * Currently only WebKit-based Web Inspectors, Firefox >= v31,
99 + * and the Firebug extension (any Firefox version) are known
100 + * to support "%c" CSS customizations.
101 + *
102 + * TODO: add a `localStorage` variable to explicitly enable/disable colors
103 + */
104 +
105 +// eslint-disable-next-line complexity
106 +function useColors() {
107 + // NB: In an Electron preload script, document will be defined but not fully
108 + // initialized. Since we know we're in Chrome, we'll just detect this case
109 + // explicitly
110 + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
111 + return true;
112 + }
113 +
114 + // Internet Explorer and Edge do not support colors.
115 + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
116 + return false;
117 + }
118 +
119 + // Is webkit? http://stackoverflow.com/a/16459606/376773
120 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
121 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
122 + // Is firebug? http://stackoverflow.com/a/398120/376773
123 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
124 + // Is firefox >= v31?
125 + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
126 + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
127 + // Double check webkit in userAgent just in case we are in a worker
128 + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
129 +}
130 +
131 +/**
132 + * Colorize log arguments if enabled.
133 + *
134 + * @api public
135 + */
136 +
137 +function formatArgs(args) {
138 + args[0] = (this.useColors ? '%c' : '') +
139 + this.namespace +
140 + (this.useColors ? ' %c' : ' ') +
141 + args[0] +
142 + (this.useColors ? '%c ' : ' ') +
143 + '+' + module.exports.humanize(this.diff);
144 +
145 + if (!this.useColors) {
146 + return;
147 + }
148 +
149 + const c = 'color: ' + this.color;
150 + args.splice(1, 0, c, 'color: inherit');
151 +
152 + // The final "%c" is somewhat tricky, because there could be other
153 + // arguments passed either before or after the %c, so we need to
154 + // figure out the correct index to insert the CSS into
155 + let index = 0;
156 + let lastC = 0;
157 + args[0].replace(/%[a-zA-Z%]/g, match => {
158 + if (match === '%%') {
159 + return;
160 + }
161 + index++;
162 + if (match === '%c') {
163 + // We only are interested in the *last* %c
164 + // (the user may have provided their own)
165 + lastC = index;
166 + }
167 + });
168 +
169 + args.splice(lastC, 0, c);
170 +}
171 +
172 +/**
173 + * Invokes `console.log()` when available.
174 + * No-op when `console.log` is not a "function".
175 + *
176 + * @api public
177 + */
178 +function log(...args) {
179 + // This hackery is required for IE8/9, where
180 + // the `console.log` function doesn't have 'apply'
181 + return typeof console === 'object' &&
182 + console.log &&
183 + console.log(...args);
184 +}
185 +
186 +/**
187 + * Save `namespaces`.
188 + *
189 + * @param {String} namespaces
190 + * @api private
191 + */
192 +function save(namespaces) {
193 + try {
194 + if (namespaces) {
195 + exports.storage.setItem('debug', namespaces);
196 + } else {
197 + exports.storage.removeItem('debug');
198 + }
199 + } catch (error) {
200 + // Swallow
201 + // XXX (@Qix-) should we be logging these?
202 + }
203 +}
204 +
205 +/**
206 + * Load `namespaces`.
207 + *
208 + * @return {String} returns the previously persisted debug modes
209 + * @api private
210 + */
211 +function load() {
212 + let r;
213 + try {
214 + r = exports.storage.getItem('debug');
215 + } catch (error) {
216 + // Swallow
217 + // XXX (@Qix-) should we be logging these?
218 + }
219 +
220 + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG
221 + if (!r && typeof process !== 'undefined' && 'env' in process) {
222 + r = process.env.DEBUG;
223 + }
224 +
225 + return r;
226 +}
227 +
228 +/**
229 + * Localstorage attempts to return the localstorage.
230 + *
231 + * This is necessary because safari throws
232 + * when a user disables cookies/localstorage
233 + * and you attempt to access it.
234 + *
235 + * @return {LocalStorage}
236 + * @api private
237 + */
238 +
239 +function localstorage() {
240 + try {
241 + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
242 + // The Browser also has localStorage in the global context.
243 + return localStorage;
244 + } catch (error) {
245 + // Swallow
246 + // XXX (@Qix-) should we be logging these?
247 + }
248 +}
249 +
250 +module.exports = require('./common')(exports);
251 +
252 +const {formatters} = module.exports;
253 +
254 +/**
255 + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
256 + */
257 +
258 +formatters.j = function (v) {
259 + try {
260 + return JSON.stringify(v);
261 + } catch (error) {
262 + return '[UnexpectedJSONParseError]: ' + error.message;
263 + }
264 +};
1 +
2 +/**
3 + * This is the common logic for both the Node.js and web browser
4 + * implementations of `debug()`.
5 + */
6 +
7 +function setup(env) {
8 + createDebug.debug = createDebug;
9 + createDebug.default = createDebug;
10 + createDebug.coerce = coerce;
11 + createDebug.disable = disable;
12 + createDebug.enable = enable;
13 + createDebug.enabled = enabled;
14 + createDebug.humanize = require('ms');
15 +
16 + Object.keys(env).forEach(key => {
17 + createDebug[key] = env[key];
18 + });
19 +
20 + /**
21 + * Active `debug` instances.
22 + */
23 + createDebug.instances = [];
24 +
25 + /**
26 + * The currently active debug mode names, and names to skip.
27 + */
28 +
29 + createDebug.names = [];
30 + createDebug.skips = [];
31 +
32 + /**
33 + * Map of special "%n" handling functions, for the debug "format" argument.
34 + *
35 + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
36 + */
37 + createDebug.formatters = {};
38 +
39 + /**
40 + * Selects a color for a debug namespace
41 + * @param {String} namespace The namespace string for the for the debug instance to be colored
42 + * @return {Number|String} An ANSI color code for the given namespace
43 + * @api private
44 + */
45 + function selectColor(namespace) {
46 + let hash = 0;
47 +
48 + for (let i = 0; i < namespace.length; i++) {
49 + hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
50 + hash |= 0; // Convert to 32bit integer
51 + }
52 +
53 + return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
54 + }
55 + createDebug.selectColor = selectColor;
56 +
57 + /**
58 + * Create a debugger with the given `namespace`.
59 + *
60 + * @param {String} namespace
61 + * @return {Function}
62 + * @api public
63 + */
64 + function createDebug(namespace) {
65 + let prevTime;
66 +
67 + function debug(...args) {
68 + // Disabled?
69 + if (!debug.enabled) {
70 + return;
71 + }
72 +
73 + const self = debug;
74 +
75 + // Set `diff` timestamp
76 + const curr = Number(new Date());
77 + const ms = curr - (prevTime || curr);
78 + self.diff = ms;
79 + self.prev = prevTime;
80 + self.curr = curr;
81 + prevTime = curr;
82 +
83 + args[0] = createDebug.coerce(args[0]);
84 +
85 + if (typeof args[0] !== 'string') {
86 + // Anything else let's inspect with %O
87 + args.unshift('%O');
88 + }
89 +
90 + // Apply any `formatters` transformations
91 + let index = 0;
92 + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
93 + // If we encounter an escaped % then don't increase the array index
94 + if (match === '%%') {
95 + return match;
96 + }
97 + index++;
98 + const formatter = createDebug.formatters[format];
99 + if (typeof formatter === 'function') {
100 + const val = args[index];
101 + match = formatter.call(self, val);
102 +
103 + // Now we need to remove `args[index]` since it's inlined in the `format`
104 + args.splice(index, 1);
105 + index--;
106 + }
107 + return match;
108 + });
109 +
110 + // Apply env-specific formatting (colors, etc.)
111 + createDebug.formatArgs.call(self, args);
112 +
113 + const logFn = self.log || createDebug.log;
114 + logFn.apply(self, args);
115 + }
116 +
117 + debug.namespace = namespace;
118 + debug.enabled = createDebug.enabled(namespace);
119 + debug.useColors = createDebug.useColors();
120 + debug.color = selectColor(namespace);
121 + debug.destroy = destroy;
122 + debug.extend = extend;
123 + // Debug.formatArgs = formatArgs;
124 + // debug.rawLog = rawLog;
125 +
126 + // env-specific initialization logic for debug instances
127 + if (typeof createDebug.init === 'function') {
128 + createDebug.init(debug);
129 + }
130 +
131 + createDebug.instances.push(debug);
132 +
133 + return debug;
134 + }
135 +
136 + function destroy() {
137 + const index = createDebug.instances.indexOf(this);
138 + if (index !== -1) {
139 + createDebug.instances.splice(index, 1);
140 + return true;
141 + }
142 + return false;
143 + }
144 +
145 + function extend(namespace, delimiter) {
146 + const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
147 + newDebug.log = this.log;
148 + return newDebug;
149 + }
150 +
151 + /**
152 + * Enables a debug mode by namespaces. This can include modes
153 + * separated by a colon and wildcards.
154 + *
155 + * @param {String} namespaces
156 + * @api public
157 + */
158 + function enable(namespaces) {
159 + createDebug.save(namespaces);
160 +
161 + createDebug.names = [];
162 + createDebug.skips = [];
163 +
164 + let i;
165 + const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
166 + const len = split.length;
167 +
168 + for (i = 0; i < len; i++) {
169 + if (!split[i]) {
170 + // ignore empty strings
171 + continue;
172 + }
173 +
174 + namespaces = split[i].replace(/\*/g, '.*?');
175 +
176 + if (namespaces[0] === '-') {
177 + createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
178 + } else {
179 + createDebug.names.push(new RegExp('^' + namespaces + '$'));
180 + }
181 + }
182 +
183 + for (i = 0; i < createDebug.instances.length; i++) {
184 + const instance = createDebug.instances[i];
185 + instance.enabled = createDebug.enabled(instance.namespace);
186 + }
187 + }
188 +
189 + /**
190 + * Disable debug output.
191 + *
192 + * @return {String} namespaces
193 + * @api public
194 + */
195 + function disable() {
196 + const namespaces = [
197 + ...createDebug.names.map(toNamespace),
198 + ...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace)
199 + ].join(',');
200 + createDebug.enable('');
201 + return namespaces;
202 + }
203 +
204 + /**
205 + * Returns true if the given mode name is enabled, false otherwise.
206 + *
207 + * @param {String} name
208 + * @return {Boolean}
209 + * @api public
210 + */
211 + function enabled(name) {
212 + if (name[name.length - 1] === '*') {
213 + return true;
214 + }
215 +
216 + let i;
217 + let len;
218 +
219 + for (i = 0, len = createDebug.skips.length; i < len; i++) {
220 + if (createDebug.skips[i].test(name)) {
221 + return false;
222 + }
223 + }
224 +
225 + for (i = 0, len = createDebug.names.length; i < len; i++) {
226 + if (createDebug.names[i].test(name)) {
227 + return true;
228 + }
229 + }
230 +
231 + return false;
232 + }
233 +
234 + /**
235 + * Convert regexp to namespace
236 + *
237 + * @param {RegExp} regxep
238 + * @return {String} namespace
239 + * @api private
240 + */
241 + function toNamespace(regexp) {
242 + return regexp.toString()
243 + .substring(2, regexp.toString().length - 2)
244 + .replace(/\.\*\?$/, '*');
245 + }
246 +
247 + /**
248 + * Coerce `val`.
249 + *
250 + * @param {Mixed} val
251 + * @return {Mixed}
252 + * @api private
253 + */
254 + function coerce(val) {
255 + if (val instanceof Error) {
256 + return val.stack || val.message;
257 + }
258 + return val;
259 + }
260 +
261 + createDebug.enable(createDebug.load());
262 +
263 + return createDebug;
264 +}
265 +
266 +module.exports = setup;
1 +/**
2 + * Detect Electron renderer / nwjs process, which is node, but we should
3 + * treat as a browser.
4 + */
5 +
6 +if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) {
7 + module.exports = require('./browser.js');
8 +} else {
9 + module.exports = require('./node.js');
10 +}
1 +/**
2 + * Module dependencies.
3 + */
4 +
5 +const tty = require('tty');
6 +const util = require('util');
7 +
8 +/**
9 + * This is the Node.js implementation of `debug()`.
10 + */
11 +
12 +exports.init = init;
13 +exports.log = log;
14 +exports.formatArgs = formatArgs;
15 +exports.save = save;
16 +exports.load = load;
17 +exports.useColors = useColors;
18 +
19 +/**
20 + * Colors.
21 + */
22 +
23 +exports.colors = [6, 2, 3, 4, 5, 1];
24 +
25 +try {
26 + // Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
27 + // eslint-disable-next-line import/no-extraneous-dependencies
28 + const supportsColor = require('supports-color');
29 +
30 + if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
31 + exports.colors = [
32 + 20,
33 + 21,
34 + 26,
35 + 27,
36 + 32,
37 + 33,
38 + 38,
39 + 39,
40 + 40,
41 + 41,
42 + 42,
43 + 43,
44 + 44,
45 + 45,
46 + 56,
47 + 57,
48 + 62,
49 + 63,
50 + 68,
51 + 69,
52 + 74,
53 + 75,
54 + 76,
55 + 77,
56 + 78,
57 + 79,
58 + 80,
59 + 81,
60 + 92,
61 + 93,
62 + 98,
63 + 99,
64 + 112,
65 + 113,
66 + 128,
67 + 129,
68 + 134,
69 + 135,
70 + 148,
71 + 149,
72 + 160,
73 + 161,
74 + 162,
75 + 163,
76 + 164,
77 + 165,
78 + 166,
79 + 167,
80 + 168,
81 + 169,
82 + 170,
83 + 171,
84 + 172,
85 + 173,
86 + 178,
87 + 179,
88 + 184,
89 + 185,
90 + 196,
91 + 197,
92 + 198,
93 + 199,
94 + 200,
95 + 201,
96 + 202,
97 + 203,
98 + 204,
99 + 205,
100 + 206,
101 + 207,
102 + 208,
103 + 209,
104 + 214,
105 + 215,
106 + 220,
107 + 221
108 + ];
109 + }
110 +} catch (error) {
111 + // Swallow - we only care if `supports-color` is available; it doesn't have to be.
112 +}
113 +
114 +/**
115 + * Build up the default `inspectOpts` object from the environment variables.
116 + *
117 + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
118 + */
119 +
120 +exports.inspectOpts = Object.keys(process.env).filter(key => {
121 + return /^debug_/i.test(key);
122 +}).reduce((obj, key) => {
123 + // Camel-case
124 + const prop = key
125 + .substring(6)
126 + .toLowerCase()
127 + .replace(/_([a-z])/g, (_, k) => {
128 + return k.toUpperCase();
129 + });
130 +
131 + // Coerce string value into JS value
132 + let val = process.env[key];
133 + if (/^(yes|on|true|enabled)$/i.test(val)) {
134 + val = true;
135 + } else if (/^(no|off|false|disabled)$/i.test(val)) {
136 + val = false;
137 + } else if (val === 'null') {
138 + val = null;
139 + } else {
140 + val = Number(val);
141 + }
142 +
143 + obj[prop] = val;
144 + return obj;
145 +}, {});
146 +
147 +/**
148 + * Is stdout a TTY? Colored output is enabled when `true`.
149 + */
150 +
151 +function useColors() {
152 + return 'colors' in exports.inspectOpts ?
153 + Boolean(exports.inspectOpts.colors) :
154 + tty.isatty(process.stderr.fd);
155 +}
156 +
157 +/**
158 + * Adds ANSI color escape codes if enabled.
159 + *
160 + * @api public
161 + */
162 +
163 +function formatArgs(args) {
164 + const {namespace: name, useColors} = this;
165 +
166 + if (useColors) {
167 + const c = this.color;
168 + const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c);
169 + const prefix = ` ${colorCode};1m${name} \u001B[0m`;
170 +
171 + args[0] = prefix + args[0].split('\n').join('\n' + prefix);
172 + args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m');
173 + } else {
174 + args[0] = getDate() + name + ' ' + args[0];
175 + }
176 +}
177 +
178 +function getDate() {
179 + if (exports.inspectOpts.hideDate) {
180 + return '';
181 + }
182 + return new Date().toISOString() + ' ';
183 +}
184 +
185 +/**
186 + * Invokes `util.format()` with the specified arguments and writes to stderr.
187 + */
188 +
189 +function log(...args) {
190 + return process.stderr.write(util.format(...args) + '\n');
191 +}
192 +
193 +/**
194 + * Save `namespaces`.
195 + *
196 + * @param {String} namespaces
197 + * @api private
198 + */
199 +function save(namespaces) {
200 + if (namespaces) {
201 + process.env.DEBUG = namespaces;
202 + } else {
203 + // If you set a process.env field to null or undefined, it gets cast to the
204 + // string 'null' or 'undefined'. Just delete instead.
205 + delete process.env.DEBUG;
206 + }
207 +}
208 +
209 +/**
210 + * Load `namespaces`.
211 + *
212 + * @return {String} returns the previously persisted debug modes
213 + * @api private
214 + */
215 +
216 +function load() {
217 + return process.env.DEBUG;
218 +}
219 +
220 +/**
221 + * Init logic for `debug` instances.
222 + *
223 + * Create a new `inspectOpts` object in case `useColors` is set
224 + * differently for a particular `debug` instance.
225 + */
226 +
227 +function init(debug) {
228 + debug.inspectOpts = {};
229 +
230 + const keys = Object.keys(exports.inspectOpts);
231 + for (let i = 0; i < keys.length; i++) {
232 + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
233 + }
234 +}
235 +
236 +module.exports = require('./common')(exports);
237 +
238 +const {formatters} = module.exports;
239 +
240 +/**
241 + * Map %o to `util.inspect()`, all on a single line.
242 + */
243 +
244 +formatters.o = function (v) {
245 + this.inspectOpts.colors = this.useColors;
246 + return util.inspect(v, this.inspectOpts)
247 + .replace(/\s*\n\s*/g, ' ');
248 +};
249 +
250 +/**
251 + * Map %O to `util.inspect()`, allowing multiple lines if needed.
252 + */
253 +
254 +formatters.O = function (v) {
255 + this.inspectOpts.colors = this.useColors;
256 + return util.inspect(v, this.inspectOpts);
257 +};
1 +/**
2 + * Helpers.
3 + */
4 +
5 +var s = 1000;
6 +var m = s * 60;
7 +var h = m * 60;
8 +var d = h * 24;
9 +var w = d * 7;
10 +var y = d * 365.25;
11 +
12 +/**
13 + * Parse or format the given `val`.
14 + *
15 + * Options:
16 + *
17 + * - `long` verbose formatting [false]
18 + *
19 + * @param {String|Number} val
20 + * @param {Object} [options]
21 + * @throws {Error} throw an error if val is not a non-empty string or a number
22 + * @return {String|Number}
23 + * @api public
24 + */
25 +
26 +module.exports = function(val, options) {
27 + options = options || {};
28 + var type = typeof val;
29 + if (type === 'string' && val.length > 0) {
30 + return parse(val);
31 + } else if (type === 'number' && isFinite(val)) {
32 + return options.long ? fmtLong(val) : fmtShort(val);
33 + }
34 + throw new Error(
35 + 'val is not a non-empty string or a valid number. val=' +
36 + JSON.stringify(val)
37 + );
38 +};
39 +
40 +/**
41 + * Parse the given `str` and return milliseconds.
42 + *
43 + * @param {String} str
44 + * @return {Number}
45 + * @api private
46 + */
47 +
48 +function parse(str) {
49 + str = String(str);
50 + if (str.length > 100) {
51 + return;
52 + }
53 + var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
54 + str
55 + );
56 + if (!match) {
57 + return;
58 + }
59 + var n = parseFloat(match[1]);
60 + var type = (match[2] || 'ms').toLowerCase();
61 + switch (type) {
62 + case 'years':
63 + case 'year':
64 + case 'yrs':
65 + case 'yr':
66 + case 'y':
67 + return n * y;
68 + case 'weeks':
69 + case 'week':
70 + case 'w':
71 + return n * w;
72 + case 'days':
73 + case 'day':
74 + case 'd':
75 + return n * d;
76 + case 'hours':
77 + case 'hour':
78 + case 'hrs':
79 + case 'hr':
80 + case 'h':
81 + return n * h;
82 + case 'minutes':
83 + case 'minute':
84 + case 'mins':
85 + case 'min':
86 + case 'm':
87 + return n * m;
88 + case 'seconds':
89 + case 'second':
90 + case 'secs':
91 + case 'sec':
92 + case 's':
93 + return n * s;
94 + case 'milliseconds':
95 + case 'millisecond':
96 + case 'msecs':
97 + case 'msec':
98 + case 'ms':
99 + return n;
100 + default:
101 + return undefined;
102 + }
103 +}
104 +
105 +/**
106 + * Short format for `ms`.
107 + *
108 + * @param {Number} ms
109 + * @return {String}
110 + * @api private
111 + */
112 +
113 +function fmtShort(ms) {
114 + var msAbs = Math.abs(ms);
115 + if (msAbs >= d) {
116 + return Math.round(ms / d) + 'd';
117 + }
118 + if (msAbs >= h) {
119 + return Math.round(ms / h) + 'h';
120 + }
121 + if (msAbs >= m) {
122 + return Math.round(ms / m) + 'm';
123 + }
124 + if (msAbs >= s) {
125 + return Math.round(ms / s) + 's';
126 + }
127 + return ms + 'ms';
128 +}
129 +
130 +/**
131 + * Long format for `ms`.
132 + *
133 + * @param {Number} ms
134 + * @return {String}
135 + * @api private
136 + */
137 +
138 +function fmtLong(ms) {
139 + var msAbs = Math.abs(ms);
140 + if (msAbs >= d) {
141 + return plural(ms, msAbs, d, 'day');
142 + }
143 + if (msAbs >= h) {
144 + return plural(ms, msAbs, h, 'hour');
145 + }
146 + if (msAbs >= m) {
147 + return plural(ms, msAbs, m, 'minute');
148 + }
149 + if (msAbs >= s) {
150 + return plural(ms, msAbs, s, 'second');
151 + }
152 + return ms + ' ms';
153 +}
154 +
155 +/**
156 + * Pluralization helper.
157 + */
158 +
159 +function plural(ms, msAbs, n, name) {
160 + var isPlural = msAbs >= n * 1.5;
161 + return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
162 +}
1 +The MIT License (MIT)
2 +
3 +Copyright (c) 2016 Zeit, Inc.
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.
1 +{
2 + "_from": "ms@^2.1.1",
3 + "_id": "ms@2.1.2",
4 + "_inBundle": false,
5 + "_integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
6 + "_location": "/messaging-api-line/ms",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "ms@^2.1.1",
12 + "name": "ms",
13 + "escapedName": "ms",
14 + "rawSpec": "^2.1.1",
15 + "saveSpec": null,
16 + "fetchSpec": "^2.1.1"
17 + },
18 + "_requiredBy": [
19 + "/messaging-api-line/debug"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
22 + "_shasum": "d09d1f357b443f493382a8eb3ccd183872ae6009",
23 + "_spec": "ms@^2.1.1",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\messaging-api-line\\node_modules\\debug",
25 + "bugs": {
26 + "url": "https://github.com/zeit/ms/issues"
27 + },
28 + "bundleDependencies": false,
29 + "deprecated": false,
30 + "description": "Tiny millisecond conversion utility",
31 + "devDependencies": {
32 + "eslint": "4.12.1",
33 + "expect.js": "0.3.1",
34 + "husky": "0.14.3",
35 + "lint-staged": "5.0.0",
36 + "mocha": "4.0.1"
37 + },
38 + "eslintConfig": {
39 + "extends": "eslint:recommended",
40 + "env": {
41 + "node": true,
42 + "es6": true
43 + }
44 + },
45 + "files": [
46 + "index.js"
47 + ],
48 + "homepage": "https://github.com/zeit/ms#readme",
49 + "license": "MIT",
50 + "lint-staged": {
51 + "*.js": [
52 + "npm run lint",
53 + "prettier --single-quote --write",
54 + "git add"
55 + ]
56 + },
57 + "main": "./index",
58 + "name": "ms",
59 + "repository": {
60 + "type": "git",
61 + "url": "git+https://github.com/zeit/ms.git"
62 + },
63 + "scripts": {
64 + "lint": "eslint lib/* bin/*",
65 + "precommit": "lint-staged",
66 + "test": "mocha tests.js"
67 + },
68 + "version": "2.1.2"
69 +}
1 +# ms
2 +
3 +[![Build Status](https://travis-ci.org/zeit/ms.svg?branch=master)](https://travis-ci.org/zeit/ms)
4 +[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit)
5 +
6 +Use this package to easily convert various time formats to milliseconds.
7 +
8 +## Examples
9 +
10 +```js
11 +ms('2 days') // 172800000
12 +ms('1d') // 86400000
13 +ms('10h') // 36000000
14 +ms('2.5 hrs') // 9000000
15 +ms('2h') // 7200000
16 +ms('1m') // 60000
17 +ms('5s') // 5000
18 +ms('1y') // 31557600000
19 +ms('100') // 100
20 +ms('-3 days') // -259200000
21 +ms('-1h') // -3600000
22 +ms('-200') // -200
23 +```
24 +
25 +### Convert from Milliseconds
26 +
27 +```js
28 +ms(60000) // "1m"
29 +ms(2 * 60000) // "2m"
30 +ms(-3 * 60000) // "-3m"
31 +ms(ms('10 hours')) // "10h"
32 +```
33 +
34 +### Time Format Written-Out
35 +
36 +```js
37 +ms(60000, { long: true }) // "1 minute"
38 +ms(2 * 60000, { long: true }) // "2 minutes"
39 +ms(-3 * 60000, { long: true }) // "-3 minutes"
40 +ms(ms('10 hours'), { long: true }) // "10 hours"
41 +```
42 +
43 +## Features
44 +
45 +- Works both in [Node.js](https://nodejs.org) and in the browser
46 +- If a number is supplied to `ms`, a string with a unit is returned
47 +- If a string that contains the number is supplied, it returns it as a number (e.g.: it returns `100` for `'100'`)
48 +- If you pass a string with a number and a valid unit, the number of equivalent milliseconds is returned
49 +
50 +## Related Packages
51 +
52 +- [ms.macro](https://github.com/knpwrs/ms.macro) - Run `ms` as a macro at build-time.
53 +
54 +## Caught a Bug?
55 +
56 +1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
57 +2. Link the package to the global module directory: `npm link`
58 +3. Within the module you want to test your local development instance of ms, just link it to the dependencies: `npm link ms`. Instead of the default one from npm, Node.js will now use your clone of ms!
59 +
60 +As always, you can run the tests using: `npm test`
1 +{
2 + "_from": "messaging-api-line",
3 + "_id": "messaging-api-line@0.8.3",
4 + "_inBundle": false,
5 + "_integrity": "sha512-EQRqW9UMtfQPw/PfnkFue7eSXZlxX2Ke5frFGhl6UHUW5Qfi4KPpvAqf2VTaU/cU7rGhGnZWuNbjoxkCWDyT8A==",
6 + "_location": "/messaging-api-line",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "tag",
10 + "registry": true,
11 + "raw": "messaging-api-line",
12 + "name": "messaging-api-line",
13 + "escapedName": "messaging-api-line",
14 + "rawSpec": "",
15 + "saveSpec": null,
16 + "fetchSpec": "latest"
17 + },
18 + "_requiredBy": [
19 + "#USER",
20 + "/"
21 + ],
22 + "_resolved": "https://registry.npmjs.org/messaging-api-line/-/messaging-api-line-0.8.3.tgz",
23 + "_shasum": "53344ba1e88bc91da5c03df5618e32a395e176e8",
24 + "_spec": "messaging-api-line",
25 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT",
26 + "browser": "lib/browser.js",
27 + "bugs": {
28 + "url": "https://github.com/Yoctol/messaging-apis/issues"
29 + },
30 + "bundleDependencies": false,
31 + "dependencies": {
32 + "axios": "^0.19.0",
33 + "axios-error": "^0.8.1",
34 + "debug": "^4.1.1",
35 + "image-type": "^4.1.0",
36 + "invariant": "^2.2.4",
37 + "lodash.omit": "^4.5.0",
38 + "url-join": "^4.0.1"
39 + },
40 + "deprecated": false,
41 + "description": "Messaging API client for LINE",
42 + "devDependencies": {
43 + "axios-mock-adapter": "^1.17.0"
44 + },
45 + "engines": {
46 + "node": ">=8"
47 + },
48 + "gitHead": "874202b421e7b302d84d9b039fbfc30b39a77ee3",
49 + "homepage": "https://github.com/Yoctol/messaging-apis#readme",
50 + "keywords": [
51 + "bot",
52 + "chatbot",
53 + "line",
54 + "messaging-apis"
55 + ],
56 + "license": "MIT",
57 + "main": "lib/index.js",
58 + "name": "messaging-api-line",
59 + "repository": {
60 + "type": "git",
61 + "url": "git+https://github.com/Yoctol/messaging-apis.git"
62 + },
63 + "version": "0.8.3"
64 +}
1 +/* @flow */
2 +import invariant from 'invariant';
3 +import omit from 'lodash.omit';
4 +
5 +import {
6 + type AudioMessage,
7 + type ButtonsTemplate,
8 + type CarouselTemplate,
9 + type ColumnObject,
10 + type ConfirmTemplate,
11 + type FlexContainer,
12 + type FlexMessage,
13 + type ImageCarouselColumnObject,
14 + type ImageCarouselTemplate,
15 + type ImageMapAction,
16 + type ImageMapMessage,
17 + type ImageMapVideo,
18 + type ImageMessage,
19 + type Location,
20 + type LocationMessage,
21 + type MessageOptions,
22 + type QuickReply,
23 + type StickerMessage,
24 + type Template,
25 + type TemplateAction,
26 + type TemplateMessage,
27 + type TextMessage,
28 + type VideoMessage,
29 +} from './LineTypes';
30 +
31 +function createText(text: string, options: MessageOptions = {}): TextMessage {
32 + return {
33 + type: 'text',
34 + text,
35 + ...omit(options, 'accessToken'),
36 + };
37 +}
38 +
39 +function createImage(
40 + contentUrlOrImage: string | Object,
41 + previewUrlOrOptions?: string | MessageOptions
42 +): ImageMessage {
43 + if (previewUrlOrOptions) {
44 + if (
45 + typeof contentUrlOrImage === 'object' &&
46 + typeof previewUrlOrOptions === 'object'
47 + ) {
48 + const image = contentUrlOrImage;
49 + const options = previewUrlOrOptions;
50 + return {
51 + type: 'image',
52 + originalContentUrl: image.originalContentUrl,
53 + previewImageUrl: image.previewImageUrl || image.originalContentUrl,
54 + ...omit(options, 'accessToken'),
55 + };
56 + }
57 +
58 + if (
59 + typeof contentUrlOrImage === 'string' &&
60 + typeof previewUrlOrOptions === 'string'
61 + ) {
62 + return {
63 + type: 'image',
64 + originalContentUrl: contentUrlOrImage,
65 + previewImageUrl: previewUrlOrOptions,
66 + };
67 + }
68 + } else {
69 + if (typeof contentUrlOrImage === 'object') {
70 + const image = contentUrlOrImage;
71 + return {
72 + type: 'image',
73 + originalContentUrl: image.originalContentUrl,
74 + previewImageUrl: image.previewImageUrl || image.originalContentUrl,
75 + };
76 + }
77 +
78 + if (typeof contentUrlOrImage === 'string') {
79 + return {
80 + type: 'image',
81 + originalContentUrl: contentUrlOrImage,
82 + previewImageUrl: contentUrlOrImage,
83 + };
84 + }
85 + }
86 +
87 + invariant(false, 'Line#createImage: Wrong type of arguments.');
88 +}
89 +
90 +function createVideo(
91 + contentUrlOrVideo: string | Object,
92 + previewImageUrlOrOptions?: string | MessageOptions
93 +): VideoMessage {
94 + if (
95 + typeof contentUrlOrVideo === 'string' &&
96 + typeof previewImageUrlOrOptions === 'string'
97 + ) {
98 + return {
99 + type: 'video',
100 + originalContentUrl: contentUrlOrVideo,
101 + previewImageUrl: previewImageUrlOrOptions,
102 + };
103 + }
104 +
105 + if (
106 + typeof contentUrlOrVideo === 'object' &&
107 + (!previewImageUrlOrOptions || typeof previewImageUrlOrOptions === 'object')
108 + ) {
109 + const video = contentUrlOrVideo;
110 + const options = previewImageUrlOrOptions || {};
111 + return {
112 + type: 'video',
113 + originalContentUrl: video.originalContentUrl,
114 + previewImageUrl: video.previewImageUrl,
115 + ...omit(options, 'accessToken'),
116 + };
117 + }
118 +
119 + invariant(false, 'Line#createVideo: Wrong type of arguments.');
120 +}
121 +
122 +function createAudio(
123 + contentUrlOrAudio: string | Object,
124 + durationOrOptions: number | MessageOptions
125 +): AudioMessage {
126 + if (
127 + typeof contentUrlOrAudio === 'string' &&
128 + typeof durationOrOptions === 'number'
129 + ) {
130 + return {
131 + type: 'audio',
132 + originalContentUrl: contentUrlOrAudio,
133 + duration: durationOrOptions,
134 + };
135 + }
136 +
137 + if (
138 + typeof contentUrlOrAudio === 'object' &&
139 + (!durationOrOptions || typeof durationOrOptions === 'object')
140 + ) {
141 + const audio = contentUrlOrAudio;
142 + const options = durationOrOptions || {};
143 + return {
144 + type: 'audio',
145 + originalContentUrl: audio.originalContentUrl,
146 + duration: audio.duration,
147 + ...omit(options, 'accessToken'),
148 + };
149 + }
150 +
151 + invariant(false, 'Line#createAudio: Wrong type of arguments.');
152 +}
153 +
154 +function createLocation(
155 + { title, address, latitude, longitude }: Location,
156 + options?: MessageOptions = {}
157 +): LocationMessage {
158 + return {
159 + type: 'location',
160 + title,
161 + address,
162 + latitude,
163 + longitude,
164 + ...omit(options, 'accessToken'),
165 + };
166 +}
167 +
168 +function createSticker(
169 + packageIdOrSticker: string | Object,
170 + stickerIdOrOptions: string | MessageOptions
171 +): StickerMessage {
172 + if (
173 + typeof packageIdOrSticker === 'string' &&
174 + typeof stickerIdOrOptions === 'string'
175 + ) {
176 + return {
177 + type: 'sticker',
178 + packageId: packageIdOrSticker,
179 + stickerId: stickerIdOrOptions,
180 + };
181 + }
182 +
183 + if (
184 + typeof packageIdOrSticker === 'object' &&
185 + (!stickerIdOrOptions || typeof stickerIdOrOptions === 'object')
186 + ) {
187 + const sticker = packageIdOrSticker;
188 + const options = stickerIdOrOptions || {};
189 + return {
190 + type: 'sticker',
191 + packageId: sticker.packageId,
192 + stickerId: sticker.stickerId,
193 + ...omit(options, 'accessToken'),
194 + };
195 + }
196 +
197 + invariant(false, 'Line#createSticker: Wrong type of arguments.');
198 +}
199 +
200 +function createImagemap(
201 + altText: string,
202 + {
203 + baseUrl,
204 + baseSize,
205 + baseHeight,
206 + baseWidth,
207 + video,
208 + actions,
209 + }: {
210 + baseUrl: string,
211 + baseSize: {
212 + height: number,
213 + width: number,
214 + },
215 + baseHeight: number,
216 + baseWidth: number,
217 + video?: ImageMapVideo,
218 + actions: Array<ImageMapAction>,
219 + },
220 + options?: MessageOptions = {}
221 +): ImageMapMessage {
222 + return {
223 + type: 'imagemap',
224 + baseUrl,
225 + altText,
226 + baseSize: baseSize || {
227 + height: baseHeight,
228 + width: baseWidth,
229 + },
230 + video,
231 + actions,
232 + ...omit(options, 'accessToken'),
233 + };
234 +}
235 +
236 +function createTemplate(
237 + altText: string,
238 + template: Template,
239 + options?: MessageOptions = {}
240 +): TemplateMessage<any> {
241 + return {
242 + type: 'template',
243 + altText,
244 + template,
245 + ...omit(options, 'accessToken'),
246 + };
247 +}
248 +
249 +function createButtonTemplate(
250 + altText: string,
251 + {
252 + thumbnailImageUrl,
253 + imageAspectRatio,
254 + imageSize,
255 + imageBackgroundColor,
256 + title,
257 + text,
258 + defaultAction,
259 + actions,
260 + }: {
261 + thumbnailImageUrl?: string,
262 + imageAspectRatio?: 'rectangle' | 'square',
263 + imageSize?: 'cover' | 'contain',
264 + imageBackgroundColor?: string,
265 + title?: string,
266 + text: string,
267 + defaultAction?: TemplateAction,
268 + actions: Array<TemplateAction>,
269 + },
270 + options: MessageOptions = {}
271 +): TemplateMessage<ButtonsTemplate> {
272 + return createTemplate(
273 + altText,
274 + {
275 + type: 'buttons',
276 + thumbnailImageUrl,
277 + imageAspectRatio,
278 + imageSize,
279 + imageBackgroundColor,
280 + title,
281 + text,
282 + defaultAction,
283 + actions,
284 + },
285 + omit(options, 'accessToken')
286 + );
287 +}
288 +
289 +function createConfirmTemplate(
290 + altText: string,
291 + {
292 + text,
293 + actions,
294 + }: {
295 + text: string,
296 + actions: Array<TemplateAction>,
297 + },
298 + options?: MessageOptions = {}
299 +): TemplateMessage<ConfirmTemplate> {
300 + return createTemplate(
301 + altText,
302 + {
303 + type: 'confirm',
304 + text,
305 + actions,
306 + },
307 + omit(options, 'accessToken')
308 + );
309 +}
310 +
311 +function createCarouselTemplate(
312 + altText: string,
313 + columns: Array<ColumnObject>,
314 + {
315 + imageAspectRatio,
316 + imageSize,
317 + quickReply,
318 + }: {
319 + imageAspectRatio?: 'rectangle' | 'square',
320 + imageSize?: 'cover' | 'contain',
321 + quickReply?: QuickReply,
322 + } = {}
323 +): TemplateMessage<CarouselTemplate> {
324 + return createTemplate(
325 + altText,
326 + {
327 + type: 'carousel',
328 + columns,
329 + imageAspectRatio,
330 + imageSize,
331 + },
332 + { quickReply }
333 + );
334 +}
335 +
336 +function createImageCarouselTemplate(
337 + altText: string,
338 + columns: Array<ImageCarouselColumnObject>,
339 + options?: MessageOptions = {}
340 +): TemplateMessage<ImageCarouselTemplate> {
341 + return createTemplate(
342 + altText,
343 + {
344 + type: 'image_carousel',
345 + columns,
346 + },
347 + omit(options, 'accessToken')
348 + );
349 +}
350 +
351 +function createFlex(
352 + altText: string,
353 + contents: FlexContainer,
354 + options?: MessageOptions = {}
355 +): FlexMessage {
356 + return {
357 + type: 'flex',
358 + altText,
359 + contents,
360 + ...omit(options, 'accessToken'),
361 + };
362 +}
363 +
364 +const Line = {
365 + createText,
366 + createImage,
367 + createVideo,
368 + createAudio,
369 + createLocation,
370 + createSticker,
371 + createImagemap,
372 + createTemplate,
373 + createButtonsTemplate: createButtonTemplate,
374 + createButtonTemplate,
375 + createConfirmTemplate,
376 + createCarouselTemplate,
377 + createImageCarouselTemplate,
378 + createFlex,
379 +};
380 +
381 +export default Line;
1 +/* @flow */
2 +import AxiosError from 'axios-error';
3 +import axios from 'axios';
4 +import debug from 'debug';
5 +import imageType from 'image-type';
6 +import invariant from 'invariant';
7 +import omit from 'lodash.omit';
8 +import urlJoin from 'url-join';
9 +
10 +import Line from './Line';
11 +import {
12 + type ColumnObject,
13 + type FlexContainer,
14 + type ImageCarouselColumnObject,
15 + type ImageMapAction,
16 + type ImageMapVideo,
17 + type LiffView,
18 + type Location,
19 + type Message,
20 + type MessageOptions,
21 + type MutationSuccessResponse,
22 + type ReplyToken,
23 + type RichMenu,
24 + type SendTarget,
25 + type SendType,
26 + type Template,
27 + type TemplateAction,
28 + type User,
29 + type UserId,
30 +} from './LineTypes';
31 +
32 +type Axios = {
33 + get: Function,
34 + post: Function,
35 + put: Function,
36 + path: Function,
37 + delete: Function,
38 +};
39 +
40 +type ClientConfig = {
41 + accessToken: string,
42 + channelSecret: string,
43 + origin?: string,
44 + onRequest?: Function,
45 +};
46 +
47 +function handleError(err) {
48 + if (err.response && err.response.data) {
49 + const { message, details } = err.response.data;
50 + let msg = `LINE API - ${message}`;
51 + if (details && details.length > 0) {
52 + details.forEach(detail => {
53 + msg += `\n- ${detail.property}: ${detail.message}`;
54 + });
55 + }
56 + throw new AxiosError(msg, err);
57 + }
58 + throw new AxiosError(err.message, err);
59 +}
60 +
61 +const debugRequest = debug('messaging-api-line');
62 +
63 +function onRequest({ method, url, body }) {
64 + debugRequest(`${method} ${url}`);
65 + if (body) {
66 + debugRequest('Outgoing request body:');
67 + debugRequest(JSON.stringify(body, null, 2));
68 + }
69 +}
70 +
71 +export default class LineClient {
72 + static connect(
73 + accessTokenOrConfig: string | ClientConfig,
74 + channelSecret: string
75 + ): LineClient {
76 + return new LineClient(accessTokenOrConfig, channelSecret);
77 + }
78 +
79 + _accessToken: string;
80 +
81 + _channelSecret: string;
82 +
83 + _onRequest: Function;
84 +
85 + _axios: Axios;
86 +
87 + constructor(
88 + accessTokenOrConfig: string | ClientConfig,
89 + channelSecret: string
90 + ) {
91 + let origin;
92 + if (accessTokenOrConfig && typeof accessTokenOrConfig === 'object') {
93 + const config = accessTokenOrConfig;
94 +
95 + this._accessToken = config.accessToken;
96 + this._channelSecret = config.channelSecret;
97 + this._onRequest = config.onRequest || onRequest;
98 + origin = config.origin;
99 + } else {
100 + this._accessToken = accessTokenOrConfig;
101 + this._channelSecret = channelSecret;
102 + this._onRequest = onRequest;
103 + }
104 +
105 + this._axios = axios.create({
106 + baseURL: `${origin || 'https://api.line.me'}/`,
107 + headers: {
108 + Authorization: `Bearer ${this._accessToken}`,
109 + 'Content-Type': 'application/json',
110 + },
111 + });
112 +
113 + this._axios.interceptors.request.use(config => {
114 + this._onRequest({
115 + method: config.method,
116 + url: urlJoin(config.baseURL, config.url),
117 + headers: {
118 + ...config.headers.common,
119 + ...config.headers[config.method],
120 + ...omit(config.headers, [
121 + 'common',
122 + 'get',
123 + 'post',
124 + 'put',
125 + 'patch',
126 + 'delete',
127 + 'head',
128 + ]),
129 + },
130 + body: config.data,
131 + });
132 + return config;
133 + });
134 + }
135 +
136 + get axios(): Axios {
137 + return this._axios;
138 + }
139 +
140 + get accessToken(): string {
141 + return this._accessToken;
142 + }
143 +
144 + _send(
145 + type: SendType,
146 + target: SendTarget,
147 + ...args: Array<any>
148 + ): Promise<MutationSuccessResponse> {
149 + if (type === 'push') {
150 + return this.push(((target: any): UserId), ...args);
151 + }
152 + if (type === 'multicast') {
153 + return this.multicast(((target: any): Array<UserId>), ...args);
154 + }
155 + return this.reply(((target: any): ReplyToken), ...args);
156 + }
157 +
158 + _sendText(
159 + type: SendType,
160 + target: SendTarget,
161 + text: string,
162 + options?: MessageOptions
163 + ): Promise<MutationSuccessResponse> {
164 + return this._send(
165 + type,
166 + target,
167 + [Line.createText(text, options || {})],
168 + options
169 + );
170 + }
171 +
172 + _sendImage(
173 + type: SendType,
174 + target: SendTarget,
175 + contentUrlOrImage: string | Object,
176 + previewUrlOrOptions?: string | MessageOptions
177 + ): Promise<MutationSuccessResponse> {
178 + return this._send(
179 + type,
180 + target,
181 + [Line.createImage(contentUrlOrImage, previewUrlOrOptions)],
182 + typeof previewUrlOrOptions === 'string' ? undefined : previewUrlOrOptions
183 + );
184 + }
185 +
186 + _sendVideo(
187 + type: SendType,
188 + target: SendTarget,
189 + contentUrlOrVideo: string | Object,
190 + previewUrlOrOptions?: string | MessageOptions
191 + ): Promise<MutationSuccessResponse> {
192 + return this._send(
193 + type,
194 + target,
195 + [Line.createVideo(contentUrlOrVideo, previewUrlOrOptions || {})],
196 + typeof previewUrlOrOptions === 'string' ? undefined : previewUrlOrOptions
197 + );
198 + }
199 +
200 + _sendAudio(
201 + type: SendType,
202 + target: SendTarget,
203 + contentUrlOrAudio: string | Object,
204 + durationOrOptions?: number | MessageOptions
205 + ): Promise<MutationSuccessResponse> {
206 + return this._send(
207 + type,
208 + target,
209 + [Line.createAudio(contentUrlOrAudio, durationOrOptions || {})],
210 + typeof durationOrOptions === 'number' ? undefined : durationOrOptions
211 + );
212 + }
213 +
214 + _sendLocation(
215 + type: SendType,
216 + target: SendTarget,
217 + { title, address, latitude, longitude }: Location,
218 + options?: MessageOptions
219 + ): Promise<MutationSuccessResponse> {
220 + return this._send(
221 + type,
222 + target,
223 + [
224 + Line.createLocation(
225 + {
226 + title,
227 + address,
228 + latitude,
229 + longitude,
230 + },
231 + options || {}
232 + ),
233 + ],
234 + options
235 + );
236 + }
237 +
238 + _sendSticker(
239 + type: SendType,
240 + target: SendTarget,
241 + packageIdOrSticker: string | Object,
242 + stickerIdOrOptions?: string | MessageOptions
243 + ): Promise<MutationSuccessResponse> {
244 + return this._send(
245 + type,
246 + target,
247 + [Line.createSticker(packageIdOrSticker, stickerIdOrOptions || {})],
248 + typeof stickerIdOrOptions === 'string' ? undefined : stickerIdOrOptions
249 + );
250 + }
251 +
252 + /**
253 + * Imagemap Message
254 + *
255 + * https://developers.line.me/en/docs/messaging-api/reference/#imagemap-message
256 + */
257 + _sendImagemap(
258 + type: SendType,
259 + target: SendTarget,
260 + altText: string,
261 + {
262 + baseUrl,
263 + baseSize,
264 + baseHeight,
265 + baseWidth,
266 + video,
267 + actions,
268 + }: {
269 + baseUrl: string,
270 + baseSize: {
271 + height: number,
272 + width: number,
273 + },
274 + baseHeight: number,
275 + baseWidth: number,
276 + video?: ImageMapVideo,
277 + actions: Array<ImageMapAction>,
278 + },
279 + options?: MessageOptions
280 + ): Promise<MutationSuccessResponse> {
281 + return this._send(
282 + type,
283 + target,
284 + [
285 + Line.createImagemap(
286 + altText,
287 + {
288 + baseUrl,
289 + baseSize,
290 + baseHeight,
291 + baseWidth,
292 + video,
293 + actions,
294 + },
295 + options || {}
296 + ),
297 + ],
298 + options
299 + );
300 + }
301 +
302 + /**
303 + * Flex Message
304 + *
305 + * https://developers.line.me/en/docs/messaging-api/reference/#flex-message
306 + */
307 + _sendFlex(
308 + type: SendType,
309 + target: SendTarget,
310 + altText: string,
311 + contents: FlexContainer,
312 + options?: MessageOptions
313 + ): Promise<MutationSuccessResponse> {
314 + return this._send(
315 + type,
316 + target,
317 + [Line.createFlex(altText, contents, options || {})],
318 + options
319 + );
320 + }
321 +
322 + /**
323 + * Template Messages
324 + *
325 + * https://developers.line.me/en/docs/messaging-api/reference/#template-messages
326 + */
327 + _sendTemplate(
328 + type: SendType,
329 + target: SendTarget,
330 + altText: string,
331 + template: Template,
332 + options?: MessageOptions
333 + ): Promise<MutationSuccessResponse> {
334 + return this._send(
335 + type,
336 + target,
337 + [Line.createTemplate(altText, template, options || {})],
338 + options
339 + );
340 + }
341 +
342 + _sendButtonTemplate(
343 + type: SendType,
344 + target: SendTarget,
345 + altText: string,
346 + {
347 + thumbnailImageUrl,
348 + imageAspectRatio,
349 + imageSize,
350 + imageBackgroundColor,
351 + title,
352 + text,
353 + defaultAction,
354 + actions,
355 + }: {
356 + thumbnailImageUrl?: string,
357 + imageAspectRatio?: 'rectangle' | 'square',
358 + imageSize?: 'cover' | 'contain',
359 + imageBackgroundColor?: string,
360 + title?: string,
361 + text: string,
362 + defaultAction?: TemplateAction,
363 + actions: Array<TemplateAction>,
364 + },
365 + options?: MessageOptions
366 + ): Promise<MutationSuccessResponse> {
367 + return this._send(
368 + type,
369 + target,
370 + [
371 + Line.createButtonTemplate(
372 + altText,
373 + {
374 + thumbnailImageUrl,
375 + imageAspectRatio,
376 + imageSize,
377 + imageBackgroundColor,
378 + title,
379 + text,
380 + defaultAction,
381 + actions,
382 + },
383 + options || {}
384 + ),
385 + ],
386 + options
387 + );
388 + }
389 +
390 + _sendConfirmTemplate(
391 + type: SendType,
392 + target: SendTarget,
393 + altText: string,
394 + {
395 + text,
396 + actions,
397 + }: {
398 + text: string,
399 + actions: Array<TemplateAction>,
400 + },
401 + options?: MessageOptions
402 + ): Promise<MutationSuccessResponse> {
403 + return this._send(
404 + type,
405 + target,
406 + [
407 + Line.createConfirmTemplate(
408 + altText,
409 + {
410 + text,
411 + actions,
412 + },
413 + options || {}
414 + ),
415 + ],
416 + options
417 + );
418 + }
419 +
420 + _sendCarouselTemplate(
421 + type: SendType,
422 + target: SendTarget,
423 + altText: string,
424 + columns: Array<ColumnObject>,
425 + {
426 + imageAspectRatio,
427 + imageSize,
428 + ...options
429 + }: {
430 + imageAspectRatio?: 'rectangle' | 'square',
431 + imageSize?: 'cover' | 'contain',
432 + options?: MessageOptions,
433 + } = {}
434 + ): Promise<MutationSuccessResponse> {
435 + return this._send(
436 + type,
437 + target,
438 + [
439 + Line.createCarouselTemplate(altText, columns, {
440 + imageAspectRatio,
441 + imageSize,
442 + ...options,
443 + }),
444 + ],
445 + options
446 + );
447 + }
448 +
449 + _sendImageCarouselTemplate(
450 + type: SendType,
451 + target: SendTarget,
452 + altText: string,
453 + columns: Array<ImageCarouselColumnObject>,
454 + options?: MessageOptions
455 + ): Promise<MutationSuccessResponse> {
456 + return this._send(
457 + type,
458 + target,
459 + [Line.createImageCarouselTemplate(altText, columns, options || {})],
460 + options
461 + );
462 + }
463 +
464 + /**
465 + * Reply Message
466 + *
467 + * https://developers.line.me/en/docs/messaging-api/reference/#send-reply-message
468 + */
469 + replyRawBody(
470 + body: {
471 + replyToken: ReplyToken,
472 + messages: Array<Message>,
473 + },
474 + { accessToken: customAccessToken }: { accessToken?: string } = {}
475 + ): Promise<MutationSuccessResponse> {
476 + return this._axios
477 + .post(
478 + '/v2/bot/message/reply',
479 + body,
480 + customAccessToken && {
481 + headers: { Authorization: `Bearer ${customAccessToken}` },
482 + }
483 + )
484 + .then(res => res.data, handleError);
485 + }
486 +
487 + reply(
488 + replyToken: ReplyToken,
489 + messages: Array<Message>,
490 + options?: Object = {}
491 + ): Promise<MutationSuccessResponse> {
492 + return this.replyRawBody({ replyToken, messages }, options);
493 + }
494 +
495 + /**
496 + * Push Message
497 + *
498 + * https://developers.line.me/en/docs/messaging-api/reference/#send-push-message
499 + */
500 + pushRawBody(
501 + body: {
502 + to: string,
503 + messages: Array<Message>,
504 + },
505 + { accessToken: customAccessToken }: { accessToken?: string } = {}
506 + ): Promise<MutationSuccessResponse> {
507 + return this._axios
508 + .post(
509 + '/v2/bot/message/push',
510 + body,
511 + customAccessToken && {
512 + headers: { Authorization: `Bearer ${customAccessToken}` },
513 + }
514 + )
515 + .then(res => res.data, handleError);
516 + }
517 +
518 + push(
519 + to: string,
520 + messages: Array<Message>,
521 + options?: Object = {}
522 + ): Promise<MutationSuccessResponse> {
523 + return this.pushRawBody({ to, messages }, options);
524 + }
525 +
526 + /**
527 + * Multicast
528 + *
529 + * https://developers.line.me/en/docs/messaging-api/reference/#send-multicast-messages
530 + */
531 + multicastRawBody(
532 + body: {
533 + to: Array<UserId>,
534 + messages: Array<Message>,
535 + },
536 + { accessToken: customAccessToken }: { accessToken?: string } = {}
537 + ): Promise<MutationSuccessResponse> {
538 + return this._axios
539 + .post(
540 + '/v2/bot/message/multicast',
541 + body,
542 + customAccessToken && {
543 + headers: { Authorization: `Bearer ${customAccessToken}` },
544 + }
545 + )
546 + .then(res => res.data, handleError);
547 + }
548 +
549 + multicast(
550 + to: Array<UserId>,
551 + messages: Array<Message>,
552 + options?: Object = {}
553 + ): Promise<MutationSuccessResponse> {
554 + return this.multicastRawBody({ to, messages }, options);
555 + }
556 +
557 + /**
558 + * Content
559 + *
560 + * https://developers.line.me/en/docs/messaging-api/reference/#get-content
561 + */
562 + retrieveMessageContent(
563 + messageId: string,
564 + { accessToken: customAccessToken }: { accessToken?: string } = {}
565 + ): Promise<Buffer> {
566 + return this._axios
567 + .get(`/v2/bot/message/${messageId}/content`, {
568 + responseType: 'arraybuffer',
569 + ...(customAccessToken
570 + ? { headers: { Authorization: `Bearer ${customAccessToken}` } }
571 + : undefined),
572 + })
573 + .then(res => res.data, handleError);
574 + }
575 +
576 + /**
577 + * Get User Profile
578 + *
579 + * https://developers.line.me/en/docs/messaging-api/reference/#get-profile
580 + * displayName, userId, pictureUrl, statusMessage
581 + */
582 + getUserProfile(
583 + userId: UserId,
584 + { accessToken: customAccessToken }: { accessToken?: string } = {}
585 + ): Promise<User> {
586 + return this._axios
587 + .get(
588 + `/v2/bot/profile/${userId}`,
589 + customAccessToken && {
590 + headers: { Authorization: `Bearer ${customAccessToken}` },
591 + }
592 + )
593 + .then(res => res.data, handleError)
594 + .catch(err => {
595 + if (err.response && err.response.status === 404) {
596 + return null;
597 + }
598 + handleError(err);
599 + });
600 + }
601 +
602 + /**
603 + * Get Group Member Profile
604 + *
605 + * https://developers.line.me/en/docs/messaging-api/reference/#get-group-member-profile
606 + */
607 + getGroupMemberProfile(
608 + groupId: string,
609 + userId: UserId,
610 + { accessToken: customAccessToken }: { accessToken?: string } = {}
611 + ) {
612 + return this._axios
613 + .get(
614 + `/v2/bot/group/${groupId}/member/${userId}`,
615 + customAccessToken && {
616 + headers: { Authorization: `Bearer ${customAccessToken}` },
617 + }
618 + )
619 + .then(res => res.data, handleError);
620 + }
621 +
622 + /**
623 + * Get Room Member Profile
624 + *
625 + * https://developers.line.me/en/docs/messaging-api/reference/#get-room-member-profile
626 + */
627 + getRoomMemberProfile(
628 + roomId: string,
629 + userId: UserId,
630 + { accessToken: customAccessToken }: { accessToken?: string } = {}
631 + ) {
632 + return this._axios
633 + .get(
634 + `/v2/bot/room/${roomId}/member/${userId}`,
635 + customAccessToken && {
636 + headers: { Authorization: `Bearer ${customAccessToken}` },
637 + }
638 + )
639 + .then(res => res.data, handleError);
640 + }
641 +
642 + /**
643 + * Get Group Member IDs
644 + *
645 + * https://developers.line.me/en/docs/messaging-api/reference/#get-group-member-user-ids
646 + */
647 + getGroupMemberIds(
648 + groupId: string,
649 + start?: string,
650 + { accessToken: customAccessToken }: { accessToken?: string } = {}
651 + ): Promise<{ memberIds: Array<string>, next?: ?string }> {
652 + return this._axios
653 + .get(
654 + `/v2/bot/group/${groupId}/members/ids${start ? `?start=${start}` : ''}`,
655 + customAccessToken && {
656 + headers: { Authorization: `Bearer ${customAccessToken}` },
657 + }
658 + )
659 + .then(res => res.data, handleError);
660 + }
661 +
662 + async getAllGroupMemberIds(
663 + groupId: string,
664 + options?: Object = {}
665 + ): Promise<Array<string>> {
666 + let allMemberIds: Array<string> = [];
667 + let continuationToken;
668 +
669 + do {
670 + // eslint-disable-next-line no-await-in-loop
671 + const { memberIds, next } = await this.getGroupMemberIds(
672 + groupId,
673 + continuationToken,
674 + options
675 + );
676 + allMemberIds = allMemberIds.concat(memberIds);
677 + continuationToken = next;
678 + } while (continuationToken);
679 +
680 + return allMemberIds;
681 + }
682 +
683 + /**
684 + * Get Room Member IDs
685 + *
686 + * https://developers.line.me/en/docs/messaging-api/reference/#get-room-member-user-ids
687 + */
688 + getRoomMemberIds(
689 + roomId: string,
690 + start?: string,
691 + { accessToken: customAccessToken }: { accessToken?: string } = {}
692 + ): Promise<{ memberIds: Array<string>, next?: ?string }> {
693 + return this._axios
694 + .get(
695 + `/v2/bot/room/${roomId}/members/ids${start ? `?start=${start}` : ''}`,
696 + customAccessToken && {
697 + headers: { Authorization: `Bearer ${customAccessToken}` },
698 + }
699 + )
700 + .then(res => res.data, handleError);
701 + }
702 +
703 + async getAllRoomMemberIds(
704 + roomId: string,
705 + options?: Object = {}
706 + ): Promise<Array<string>> {
707 + let allMemberIds: Array<string> = [];
708 + let continuationToken;
709 +
710 + do {
711 + // eslint-disable-next-line no-await-in-loop
712 + const { memberIds, next } = await this.getRoomMemberIds(
713 + roomId,
714 + continuationToken,
715 + options
716 + );
717 + allMemberIds = allMemberIds.concat(memberIds);
718 + continuationToken = next;
719 + } while (continuationToken);
720 +
721 + return allMemberIds;
722 + }
723 +
724 + /**
725 + * Leave Group
726 + *
727 + * https://developers.line.me/en/docs/messaging-api/reference/#leave-group
728 + */
729 + leaveGroup(
730 + groupId: string,
731 + { accessToken: customAccessToken }: { accessToken?: string } = {}
732 + ): Promise<MutationSuccessResponse> {
733 + return this._axios
734 + .post(
735 + `/v2/bot/group/${groupId}/leave`,
736 + null,
737 + customAccessToken && {
738 + headers: { Authorization: `Bearer ${customAccessToken}` },
739 + }
740 + )
741 + .then(res => res.data, handleError);
742 + }
743 +
744 + /**
745 + * Leave Room
746 + *
747 + * https://developers.line.me/en/docs/messaging-api/reference/#leave-room
748 + */
749 + leaveRoom(
750 + roomId: string,
751 + { accessToken: customAccessToken }: { accessToken?: string } = {}
752 + ): Promise<MutationSuccessResponse> {
753 + return this._axios
754 + .post(
755 + `/v2/bot/room/${roomId}/leave`,
756 + null,
757 + customAccessToken && {
758 + headers: { Authorization: `Bearer ${customAccessToken}` },
759 + }
760 + )
761 + .then(res => res.data, handleError);
762 + }
763 +
764 + /**
765 + * Rich Menu
766 + *
767 + * https://developers.line.me/en/docs/messaging-api/reference/#rich-menu
768 + */
769 + getRichMenuList({
770 + accessToken: customAccessToken,
771 + }: { accessToken?: string } = {}) {
772 + return this._axios
773 + .get(
774 + '/v2/bot/richmenu/list',
775 + customAccessToken && {
776 + headers: { Authorization: `Bearer ${customAccessToken}` },
777 + }
778 + )
779 + .then(res => res.data.richmenus, handleError);
780 + }
781 +
782 + getRichMenu(
783 + richMenuId: string,
784 + { accessToken: customAccessToken }: { accessToken?: string } = {}
785 + ) {
786 + return this._axios
787 + .get(
788 + `/v2/bot/richmenu/${richMenuId}`,
789 + customAccessToken && {
790 + headers: { Authorization: `Bearer ${customAccessToken}` },
791 + }
792 + )
793 + .then(res => res.data)
794 + .catch(err => {
795 + if (err.response && err.response.status === 404) {
796 + return null;
797 + }
798 + handleError(err);
799 + });
800 + }
801 +
802 + createRichMenu(
803 + richMenu: RichMenu,
804 + { accessToken: customAccessToken }: { accessToken?: string } = {}
805 + ) {
806 + return this._axios
807 + .post(
808 + '/v2/bot/richmenu',
809 + richMenu,
810 + customAccessToken && {
811 + headers: { Authorization: `Bearer ${customAccessToken}` },
812 + }
813 + )
814 + .then(res => res.data, handleError);
815 + }
816 +
817 + deleteRichMenu(
818 + richMenuId: string,
819 + { accessToken: customAccessToken }: { accessToken?: string } = {}
820 + ) {
821 + return this._axios
822 + .delete(
823 + `/v2/bot/richmenu/${richMenuId}`,
824 + customAccessToken && {
825 + headers: { Authorization: `Bearer ${customAccessToken}` },
826 + }
827 + )
828 + .then(res => res.data, handleError);
829 + }
830 +
831 + getLinkedRichMenu(
832 + userId: string,
833 + { accessToken: customAccessToken }: { accessToken?: string } = {}
834 + ) {
835 + return this._axios
836 + .get(
837 + `/v2/bot/user/${userId}/richmenu`,
838 + customAccessToken && {
839 + headers: { Authorization: `Bearer ${customAccessToken}` },
840 + }
841 + )
842 + .then(res => res.data)
843 + .catch(err => {
844 + if (err.response && err.response.status === 404) {
845 + return null;
846 + }
847 + handleError(err);
848 + });
849 + }
850 +
851 + linkRichMenu(
852 + userId: string,
853 + richMenuId: string,
854 + { accessToken: customAccessToken }: { accessToken?: string } = {}
855 + ) {
856 + return this._axios
857 + .post(
858 + `/v2/bot/user/${userId}/richmenu/${richMenuId}`,
859 + null,
860 + customAccessToken && {
861 + headers: { Authorization: `Bearer ${customAccessToken}` },
862 + }
863 + )
864 + .then(res => res.data, handleError);
865 + }
866 +
867 + unlinkRichMenu(
868 + userId: string,
869 + { accessToken: customAccessToken }: { accessToken?: string } = {}
870 + ) {
871 + return this._axios
872 + .delete(
873 + `/v2/bot/user/${userId}/richmenu`,
874 + customAccessToken && {
875 + headers: { Authorization: `Bearer ${customAccessToken}` },
876 + }
877 + )
878 + .then(res => res.data, handleError);
879 + }
880 +
881 + getDefaultRichMenu({
882 + accessToken: customAccessToken,
883 + }: { accessToken?: string } = {}) {
884 + return this._axios
885 + .get(
886 + `/v2/bot/user/all/richmenu`,
887 + customAccessToken && {
888 + headers: { Authorization: `Bearer ${customAccessToken}` },
889 + }
890 + )
891 + .then(res => res.data)
892 + .catch(err => {
893 + if (err.response && err.response.status === 404) {
894 + return null;
895 + }
896 + handleError(err);
897 + });
898 + }
899 +
900 + setDefaultRichMenu(
901 + richMenuId: string,
902 + { accessToken: customAccessToken }: { accessToken?: string } = {}
903 + ) {
904 + return this._axios
905 + .post(
906 + `/v2/bot/user/all/richmenu/${richMenuId}`,
907 + null,
908 + customAccessToken && {
909 + headers: { Authorization: `Bearer ${customAccessToken}` },
910 + }
911 + )
912 + .then(res => res.data, handleError);
913 + }
914 +
915 + deleteDefaultRichMenu({
916 + accessToken: customAccessToken,
917 + }: { accessToken?: string } = {}) {
918 + return this._axios
919 + .delete(
920 + `/v2/bot/user/all/richmenu`,
921 + customAccessToken && {
922 + headers: { Authorization: `Bearer ${customAccessToken}` },
923 + }
924 + )
925 + .then(res => res.data, handleError);
926 + }
927 +
928 + /**
929 + * - Images must have one of the following resolutions: 2500x1686, 2500x843.
930 + * - You cannot replace an image attached to a rich menu.
931 + * To update your rich menu image, create a new rich menu object and upload another image.
932 + */
933 + uploadRichMenuImage(
934 + richMenuId: string,
935 + image: Buffer,
936 + { accessToken: customAccessToken }: { accessToken?: string } = {}
937 + ) {
938 + const type = imageType(image);
939 + invariant(
940 + type && (type.mime === 'image/jpeg' || type.mime === 'image/png'),
941 + 'Image must be `image/jpeg` or `image/png`'
942 + );
943 + return this._axios
944 + .post(`/v2/bot/richmenu/${richMenuId}/content`, image, {
945 + headers: customAccessToken
946 + ? {
947 + 'Content-Type': type.mime,
948 + Authorization: `Bearer ${customAccessToken}`,
949 + }
950 + : {
951 + 'Content-Type': type.mime,
952 + },
953 + })
954 + .then(res => res.data, handleError);
955 + }
956 +
957 + downloadRichMenuImage(
958 + richMenuId: string,
959 + { accessToken: customAccessToken }: { accessToken?: string } = {}
960 + ) {
961 + return this._axios
962 + .get(
963 + `/v2/bot/richmenu/${richMenuId}/content`,
964 + customAccessToken
965 + ? {
966 + responseType: 'arraybuffer',
967 + headers: {
968 + Authorization: `Bearer ${customAccessToken}`,
969 + },
970 + }
971 + : {
972 + responseType: 'arraybuffer',
973 + }
974 + )
975 + .then(res => Buffer.from(res.data))
976 + .catch(err => {
977 + if (err.response && err.response.status === 404) {
978 + return null;
979 + }
980 + handleError(err);
981 + });
982 + }
983 +
984 + /**
985 + * Account link
986 + *
987 + * https://developers.line.me/en/docs/messaging-api/reference/#account-link
988 + */
989 +
990 + issueLinkToken(
991 + userId: string,
992 + { accessToken: customAccessToken }: { accessToken?: string } = {}
993 + ): Promise<{ issueToken: string }> {
994 + return this._axios
995 + .post(
996 + `/v2/bot/user/${userId}/linkToken`,
997 + null,
998 + customAccessToken && {
999 + headers: { Authorization: `Bearer ${customAccessToken}` },
1000 + }
1001 + )
1002 + .then(res => res.data, handleError);
1003 + }
1004 +
1005 + /**
1006 + * LINE Front-end Framework (LIFF)
1007 + *
1008 + * https://developers.line.me/en/docs/liff/reference/#add-liff-app
1009 + */
1010 + getLiffAppList({
1011 + accessToken: customAccessToken,
1012 + }: { accessToken?: string } = {}): Promise<{
1013 + liffId: string,
1014 + view: LiffView,
1015 + }> {
1016 + return this._axios
1017 + .get(
1018 + '/liff/v1/apps',
1019 + customAccessToken && {
1020 + headers: { Authorization: `Bearer ${customAccessToken}` },
1021 + }
1022 + )
1023 + .then(res => res.data.apps, handleError);
1024 + }
1025 +
1026 + createLiffApp(
1027 + view: LiffView,
1028 + { accessToken: customAccessToken }: { accessToken?: string } = {}
1029 + ): Promise<{ liffId: string }> {
1030 + return this._axios
1031 + .post(
1032 + '/liff/v1/apps',
1033 + view,
1034 + customAccessToken && {
1035 + headers: { Authorization: `Bearer ${customAccessToken}` },
1036 + }
1037 + )
1038 + .then(res => res.data, handleError);
1039 + }
1040 +
1041 + updateLiffApp(
1042 + liffId: string,
1043 + view: LiffView,
1044 + { accessToken: customAccessToken }: { accessToken?: string } = {}
1045 + ): Promise<void> {
1046 + return this._axios
1047 + .put(
1048 + `/liff/v1/apps/${liffId}/view`,
1049 + view,
1050 + customAccessToken && {
1051 + headers: { Authorization: `Bearer ${customAccessToken}` },
1052 + }
1053 + )
1054 + .then(res => res.data, handleError);
1055 + }
1056 +
1057 + deleteLiffApp(
1058 + liffId: string,
1059 + { accessToken: customAccessToken }: { accessToken?: string } = {}
1060 + ): Promise<void> {
1061 + return this._axios
1062 + .delete(
1063 + `/liff/v1/apps/${liffId}`,
1064 + customAccessToken && {
1065 + headers: { Authorization: `Bearer ${customAccessToken}` },
1066 + }
1067 + )
1068 + .then(res => res.data, handleError);
1069 + }
1070 +}
1071 +
1072 +const sendTypes = ['reply', 'push', 'multicast'];
1073 +
1074 +const messageTypes: Array<{
1075 + name: string,
1076 + aliases?: Array<string>,
1077 +}> = [
1078 + { name: 'Text' },
1079 + { name: 'Image' },
1080 + { name: 'Video' },
1081 + { name: 'Audio' },
1082 + { name: 'Location' },
1083 + { name: 'Sticker' },
1084 + { name: 'Imagemap' },
1085 + { name: 'Flex' },
1086 + { name: 'Template' },
1087 + { name: 'ButtonTemplate', aliases: ['ButtonsTemplate'] },
1088 + { name: 'ConfirmTemplate' },
1089 + { name: 'CarouselTemplate' },
1090 + { name: 'ImageCarouselTemplate' },
1091 +];
1092 +
1093 +messageTypes.forEach(({ name, aliases }) => {
1094 + sendTypes.forEach(sendType => {
1095 + [name].concat(aliases || []).forEach(type => {
1096 + Object.defineProperty(LineClient.prototype, `${sendType}${type}`, {
1097 + enumerable: false,
1098 + configurable: true,
1099 + writable: true,
1100 + value(target: SendTarget, ...args) {
1101 + return this[`_send${name}`](sendType, target, ...args);
1102 + },
1103 + });
1104 + });
1105 + });
1106 +});
1 +/* @flow */
2 +import querystring from 'querystring';
3 +
4 +import AxiosError from 'axios-error';
5 +import axios from 'axios';
6 +import invariant from 'invariant';
7 +
8 +type Axios = {
9 + get: Function,
10 + post: Function,
11 + put: Function,
12 + path: Function,
13 + delete: Function,
14 +};
15 +
16 +type LinePayConfig = {
17 + channelId: string,
18 + channelSecret: string,
19 + sandbox: boolean,
20 + origin?: string,
21 +};
22 +
23 +type LinePayCurrency = 'USD' | 'JPY' | 'TWD' | 'THB';
24 +
25 +function handleError(err) {
26 + if (err.response && err.response.data) {
27 + const { returnCode, returnMessage } = err.response.data;
28 + const msg = `LINE PAY API - ${returnCode} ${returnMessage}`;
29 + throw new AxiosError(msg, err);
30 + }
31 + throw new AxiosError(err.message, err);
32 +}
33 +
34 +function throwWhenNotSuccess(res) {
35 + if (res.data.returnCode !== '0000') {
36 + const { returnCode, returnMessage } = res.data;
37 + const msg = `LINE PAY API - ${returnCode} ${returnMessage}`;
38 + throw new AxiosError(msg);
39 + }
40 + return res.data.info;
41 +}
42 +
43 +export default class LinePay {
44 + static connect(config: LinePayConfig): LinePay {
45 + return new LinePay(config);
46 + }
47 +
48 + _axios: Axios;
49 +
50 + constructor({
51 + channelId,
52 + channelSecret,
53 + sandbox = false,
54 + origin,
55 + }: LinePayConfig) {
56 + const linePayOrigin = sandbox
57 + ? 'https://sandbox-api-pay.line.me'
58 + : 'https://api-pay.line.me';
59 +
60 + this._axios = axios.create({
61 + baseURL: `${origin || linePayOrigin}/v2/`,
62 + headers: {
63 + 'Content-Type': 'application/json',
64 + 'X-LINE-ChannelId': channelId,
65 + 'X-LINE-ChannelSecret': channelSecret,
66 + },
67 + });
68 + }
69 +
70 + get axios(): Axios {
71 + return this._axios;
72 + }
73 +
74 + getPayments({
75 + transactionId,
76 + orderId,
77 + }: {
78 + transactionId: string,
79 + orderId: string,
80 + } = {}) {
81 + invariant(
82 + transactionId || orderId,
83 + 'getPayments: One of `transactionId` or `orderId` must be provided'
84 + );
85 +
86 + const query = {};
87 +
88 + if (transactionId) {
89 + query.transactionId = transactionId;
90 + }
91 +
92 + if (orderId) {
93 + query.orderId = orderId;
94 + }
95 +
96 + return this._axios
97 + .get(`/payments?${querystring.stringify(query)}`)
98 + .then(throwWhenNotSuccess, handleError);
99 + }
100 +
101 + getAuthorizations({
102 + transactionId,
103 + orderId,
104 + }: {
105 + transactionId: string,
106 + orderId: string,
107 + } = {}) {
108 + invariant(
109 + transactionId || orderId,
110 + 'getAuthorizations: One of `transactionId` or `orderId` must be provided'
111 + );
112 +
113 + const query = {};
114 +
115 + if (transactionId) {
116 + query.transactionId = transactionId;
117 + }
118 +
119 + if (orderId) {
120 + query.orderId = orderId;
121 + }
122 +
123 + return this._axios
124 + .get(`/payments/authorizations?${querystring.stringify(query)}`)
125 + .then(throwWhenNotSuccess, handleError);
126 + }
127 +
128 + reserve({
129 + productName,
130 + amount,
131 + currency,
132 + confirmUrl,
133 + orderId,
134 + ...options
135 + }: {
136 + productName: string,
137 + amount: number,
138 + currency: LinePayCurrency,
139 + confirmUrl: string,
140 + orderId: string,
141 + productImageUrl?: string,
142 + mid?: string,
143 + oneTimeKey?: string,
144 + confirmUrlType?: 'CLIENT' | 'SERVER',
145 + checkConfirmUrlBrowser?: boolean,
146 + cancelUrl?: string,
147 + packageName?: string,
148 + deliveryPlacePhone?: string,
149 + payType?: 'NORMAL' | 'PREAPPROVED',
150 + langCd?: 'ja' | 'ko' | 'en' | 'zh-Hans' | 'zh-Hant' | 'th',
151 + capture?: boolean,
152 + extras?: Object,
153 + }) {
154 + return this._axios
155 + .post('/payments/request', {
156 + productName,
157 + amount,
158 + currency,
159 + confirmUrl,
160 + orderId,
161 + ...options,
162 + })
163 + .then(throwWhenNotSuccess, handleError);
164 + }
165 +
166 + confirm(
167 + transactionId: string,
168 + {
169 + amount,
170 + currency,
171 + }: {
172 + amount: number,
173 + currency: LinePayCurrency,
174 + }
175 + ) {
176 + return this._axios
177 + .post(`/payments/${transactionId}/confirm`, {
178 + amount,
179 + currency,
180 + })
181 + .then(throwWhenNotSuccess, handleError);
182 + }
183 +
184 + capture(
185 + transactionId: string,
186 + {
187 + amount,
188 + currency,
189 + }: {
190 + amount: number,
191 + currency: LinePayCurrency,
192 + }
193 + ) {
194 + return this._axios
195 + .post(`/payments/authorizations/${transactionId}/capture`, {
196 + amount,
197 + currency,
198 + })
199 + .then(throwWhenNotSuccess, handleError);
200 + }
201 +
202 + void(transactionId: string) {
203 + return this._axios
204 + .post(`/payments/authorizations/${transactionId}/void`)
205 + .then(throwWhenNotSuccess, handleError);
206 + }
207 +
208 + refund(transactionId: string, options?: { refundAmount?: number } = {}) {
209 + return this._axios
210 + .post(`/payments/${transactionId}/refund`, options)
211 + .then(throwWhenNotSuccess, handleError);
212 + }
213 +}
1 +/* @flow */
2 +
3 +export type SendType = 'reply' | 'push' | 'multicast';
4 +
5 +export type ReplyToken = string;
6 +export type UserId = string;
7 +
8 +export type SendTarget = ReplyToken | UserId | Array<UserId>;
9 +
10 +export type User = {
11 + displayName: string,
12 + userId: string,
13 + pictureUrl: string,
14 + statusMessage: string,
15 +};
16 +
17 +export type TextMessage = {
18 + type: 'text',
19 + text: string,
20 +};
21 +
22 +export type ImageMessage = {
23 + type: 'image',
24 + originalContentUrl: string,
25 + previewImageUrl: string,
26 +};
27 +
28 +export type ImageMapVideo = {
29 + originalContentUrl: string,
30 + previewImageUrl: string,
31 + area: {
32 + x: number,
33 + y: number,
34 + width: number,
35 + height: number,
36 + },
37 + externalLink: {
38 + linkUri: string,
39 + label: string,
40 + },
41 +};
42 +
43 +export type ImageMapAction = {
44 + type: string,
45 + linkUri: string,
46 + area: {
47 + x: number,
48 + y: number,
49 + width: number,
50 + height: number,
51 + },
52 +};
53 +
54 +export type ImageMapMessage = {
55 + type: 'imagemap',
56 + baseUrl: string,
57 + altText: string,
58 + baseSize: {
59 + height: number,
60 + width: number,
61 + },
62 + actions: Array<ImageMapAction>,
63 +};
64 +
65 +export type VideoMessage = {
66 + type: 'video',
67 + originalContentUrl: string,
68 + previewImageUrl: string,
69 +};
70 +
71 +export type AudioMessage = {
72 + type: 'audio',
73 + originalContentUrl: string,
74 + duration: number,
75 +};
76 +
77 +export type Location = {
78 + title: string,
79 + address: string,
80 + latitude: number,
81 + longitude: number,
82 +};
83 +
84 +export type LocationMessage = {
85 + type: 'location',
86 + title: string,
87 + address: string,
88 + latitude: number,
89 + longitude: number,
90 +};
91 +
92 +export type StickerMessage = {
93 + type: 'sticker',
94 + packageId: string,
95 + stickerId: string,
96 +};
97 +
98 +export type PostbackAction = {
99 + type: 'postback',
100 + label?: string,
101 + data: string,
102 + text?: string,
103 + displayText?: string,
104 +};
105 +
106 +export type MessageAction = {
107 + type: 'message',
108 + label?: string,
109 + text: string,
110 +};
111 +
112 +export type URIAction = {
113 + type: 'uri',
114 + label?: string,
115 + uri: string,
116 +};
117 +
118 +export type DatetimePickerAction = {
119 + type: 'datetimepicker',
120 + label?: string,
121 + data: string,
122 + mode: string,
123 + initial?: string,
124 + max?: string,
125 + min?: string,
126 +};
127 +
128 +export type CameraAction = {
129 + type: 'camera',
130 + label: string,
131 +};
132 +
133 +export type CameraRollAction = {
134 + type: 'cameraRoll',
135 + label: string,
136 +};
137 +
138 +export type LocationAction = {
139 + type: 'location',
140 + label: string,
141 +};
142 +
143 +export type TemplateAction =
144 + | PostbackAction
145 + | MessageAction
146 + | URIAction
147 + | DatetimePickerAction;
148 +
149 +export type QuickReplyAction =
150 + | PostbackAction
151 + | MessageAction
152 + | DatetimePickerAction
153 + | CameraAction
154 + | CameraRollAction
155 + | LocationAction;
156 +
157 +export type QuickReply = {
158 + items: Array<{
159 + type: 'action',
160 + imageUrl?: string,
161 + action: QuickReplyAction,
162 + }>,
163 +};
164 +
165 +export type MessageOptions = {
166 + quickReply?: QuickReply,
167 +};
168 +
169 +export type TemplateMessage<Template> = {
170 + type: 'template',
171 + altText: string,
172 + template: Template,
173 +};
174 +
175 +export type ButtonsTemplate = {
176 + type: 'buttons',
177 + thumbnailImageUrl?: string,
178 + title?: string,
179 + text: string,
180 + defaultAction?: TemplateAction,
181 + actions: Array<TemplateAction>,
182 +};
183 +
184 +export type ConfirmTemplate = {
185 + type: 'confirm',
186 + text: string,
187 + actions: Array<TemplateAction>,
188 +};
189 +
190 +export type ColumnObject = {
191 + thumbnailImageUrl?: string,
192 + title?: string,
193 + text: string,
194 + defaultAction?: TemplateAction,
195 + actions: Array<TemplateAction>,
196 +};
197 +
198 +export type CarouselTemplate = {
199 + type: 'carousel',
200 + columns: Array<ColumnObject>,
201 +};
202 +
203 +export type ImageCarouselColumnObject = {
204 + imageUrl: string,
205 + action: TemplateAction,
206 +};
207 +
208 +export type ImageCarouselTemplate = {
209 + type: 'image_carousel',
210 + columns: Array<ImageCarouselColumnObject>,
211 +};
212 +
213 +export type Template =
214 + | ButtonsTemplate
215 + | ConfirmTemplate
216 + | CarouselTemplate
217 + | ImageCarouselTemplate;
218 +
219 +type Size = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
220 +
221 +type FlexBlockStyle = {
222 + backgroundColor?: string,
223 + separator?: boolean,
224 + separatorColor?: string,
225 +};
226 +
227 +type FlexBubbleStyle = {
228 + header?: FlexBlockStyle,
229 + hero?: FlexBlockStyle,
230 + body?: FlexBlockStyle,
231 + footer?: FlexBlockStyle,
232 +};
233 +
234 +type FlexButton = {
235 + type: 'button',
236 + action: TemplateAction,
237 + flex?: number,
238 + margin?: Size,
239 + height?: 'sm' | 'md',
240 + style?: 'link' | 'primary' | 'secondary',
241 + color?: string,
242 + gravity?: string,
243 +};
244 +
245 +type FlexFiller = {
246 + type: 'filler',
247 +};
248 +
249 +type FlexIcon = {
250 + type: 'icon',
251 + url: string,
252 + margin?: Size,
253 + size?:
254 + | 'xxs'
255 + | 'xs'
256 + | 'sm'
257 + | 'md'
258 + | 'lg'
259 + | 'xl'
260 + | 'xxl'
261 + | '3xl'
262 + | '4xl'
263 + | '5xl',
264 + asprctRatio?: '1:1' | '2:1' | '3:1',
265 +};
266 +
267 +type FlexImage = {
268 + type: 'image',
269 + url: string,
270 + flex?: number,
271 + margin?: Size,
272 + align?: 'start' | 'end' | 'center',
273 + gravity?: 'top' | 'bottom' | 'center',
274 + size?:
275 + | 'xxs'
276 + | 'xs'
277 + | 'sm'
278 + | 'md'
279 + | 'lg'
280 + | 'xl'
281 + | 'xxl'
282 + | '3xl'
283 + | '4xl'
284 + | '5xl'
285 + | 'full',
286 + aspectRatio?:
287 + | '1:1'
288 + | '1.51:1'
289 + | '1.91:1'
290 + | '4:3'
291 + | '16:9'
292 + | '20:13'
293 + | '2:1'
294 + | '3:1'
295 + | '3:4'
296 + | '9:16'
297 + | '1:2'
298 + | '1:3',
299 + aspectMode?: 'cover' | 'fit',
300 + backgroundColor?: string,
301 + action?: TemplateAction,
302 +};
303 +
304 +type FlexSeparator = {
305 + type: 'separator',
306 + margin?: Size,
307 + color?: string,
308 +};
309 +
310 +type FlexSpacer = {
311 + type: 'spacer',
312 + size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl',
313 +};
314 +
315 +type FlexText = {
316 + type: 'text',
317 + text: string,
318 + flex?: number,
319 + margin?: Size,
320 + size?:
321 + | 'xxs'
322 + | 'xs'
323 + | 'sm'
324 + | 'md'
325 + | 'lg'
326 + | 'xl'
327 + | 'xxl'
328 + | '3xl'
329 + | '4xl'
330 + | '5xl',
331 + align?: 'start' | 'end' | 'center',
332 + gravity?: 'top' | 'bottom' | 'center',
333 + wrap?: boolean,
334 + maxLines?: number,
335 + weight?: 'regular' | 'bold',
336 + color?: string,
337 + action?: TemplateAction,
338 +};
339 +
340 +type FlexBoxContent =
341 + // content
342 + | FlexButton
343 + | FlexIcon
344 + | FlexImage
345 + | FlexText
346 + // layout
347 + | FlexFiller
348 + | FlexSeparator
349 + | FlexSpacer;
350 +
351 +type FlexBox = {
352 + type: 'box',
353 + layout: 'horizontal' | 'vertical' | 'baseline',
354 + contents: Array<FlexBox | FlexBoxContent>,
355 + flex?: number,
356 + spacing?: Size,
357 + margin?: Size,
358 + action?: TemplateAction,
359 +};
360 +
361 +type FlexBubbleContainer = {
362 + type: 'bubble',
363 + direction?: 'ltr' | 'rtl',
364 + header?: FlexBox,
365 + hero?: FlexImage,
366 + body?: FlexBox,
367 + footer?: FlexBox,
368 + styles?: FlexBubbleStyle,
369 +};
370 +
371 +type FlexCarouselContainer = {
372 + type: 'carousel',
373 + contents: Array<FlexBubbleContainer>,
374 +};
375 +
376 +export type FlexContainer = FlexBubbleContainer | FlexCarouselContainer;
377 +
378 +export type FlexMessage = {
379 + type: 'flex',
380 + altText: string,
381 + contents: FlexContainer,
382 +};
383 +
384 +export type Message =
385 + | TextMessage
386 + | ImageMessage
387 + | ImageMapMessage
388 + | VideoMessage
389 + | AudioMessage
390 + | LocationMessage
391 + | StickerMessage
392 + | TemplateMessage<Template>
393 + | FlexMessage;
394 +
395 +type Area = {
396 + bounds: {
397 + x: number,
398 + y: number,
399 + width: number,
400 + height: number,
401 + },
402 + action: {
403 + type: string,
404 + data: string,
405 + },
406 +};
407 +
408 +export type RichMenu = {
409 + size: {
410 + width: 2500,
411 + height: 1686 | 843,
412 + },
413 + selected: boolean,
414 + name: string,
415 + chatBarText: string,
416 + areas: Array<Area>,
417 +};
418 +
419 +export type LiffView = {
420 + type: 'compact' | 'tall' | 'full',
421 + url: string,
422 +};
423 +
424 +export type MutationSuccessResponse = {};
1 +import invariant from 'invariant';
2 +
3 +import Line from '../Line';
4 +
5 +jest.mock('invariant');
6 +
7 +const quickReplyOptions = {
8 + quickReply: {
9 + items: [
10 + {
11 + type: 'action',
12 + action: {
13 + type: 'cameraRoll',
14 + label: 'Send photo',
15 + },
16 + },
17 + {
18 + type: 'action',
19 + action: {
20 + type: 'camera',
21 + label: 'Open camera',
22 + },
23 + },
24 + ],
25 + },
26 +};
27 +
28 +describe('#createText', () => {
29 + it('should return text message object', () => {
30 + expect(Line.createText('t')).toEqual({ type: 'text', text: 't' });
31 + expect(Line.createText('t', quickReplyOptions)).toEqual({
32 + type: 'text',
33 + text: 't',
34 + ...quickReplyOptions,
35 + });
36 + });
37 +});
38 +
39 +describe('#createImage', () => {
40 + it('should return image message object', () => {
41 + expect(Line.createImage('http://example.com/img1.jpg')).toEqual({
42 + type: 'image',
43 + originalContentUrl: 'http://example.com/img1.jpg',
44 + previewImageUrl: 'http://example.com/img1.jpg',
45 + });
46 +
47 + expect(
48 + Line.createImage(
49 + 'http://example.com/img1.jpg',
50 + 'http://example.com/img2.jpg'
51 + )
52 + ).toEqual({
53 + type: 'image',
54 + originalContentUrl: 'http://example.com/img1.jpg',
55 + previewImageUrl: 'http://example.com/img2.jpg',
56 + });
57 + });
58 +
59 + it('should work with object', () => {
60 + expect(
61 + Line.createImage({
62 + originalContentUrl: 'http://example.com/img1.jpg',
63 + })
64 + ).toEqual({
65 + type: 'image',
66 + originalContentUrl: 'http://example.com/img1.jpg',
67 + previewImageUrl: 'http://example.com/img1.jpg',
68 + });
69 +
70 + expect(
71 + Line.createImage({
72 + originalContentUrl: 'http://example.com/img1.jpg',
73 + previewImageUrl: 'http://example.com/img2.jpg',
74 + })
75 + ).toEqual({
76 + type: 'image',
77 + originalContentUrl: 'http://example.com/img1.jpg',
78 + previewImageUrl: 'http://example.com/img2.jpg',
79 + });
80 +
81 + expect(
82 + Line.createImage(
83 + {
84 + originalContentUrl: 'http://example.com/img1.jpg',
85 + },
86 + quickReplyOptions
87 + )
88 + ).toEqual({
89 + type: 'image',
90 + originalContentUrl: 'http://example.com/img1.jpg',
91 + previewImageUrl: 'http://example.com/img1.jpg',
92 + ...quickReplyOptions,
93 + });
94 +
95 + expect(
96 + Line.createImage(
97 + {
98 + originalContentUrl: 'http://example.com/img1.jpg',
99 + previewImageUrl: 'http://example.com/img2.jpg',
100 + },
101 + quickReplyOptions
102 + )
103 + ).toEqual({
104 + type: 'image',
105 + originalContentUrl: 'http://example.com/img1.jpg',
106 + previewImageUrl: 'http://example.com/img2.jpg',
107 + ...quickReplyOptions,
108 + });
109 + });
110 +
111 + it('call invariant if wrong argument type #1', () => {
112 + Line.createImage('http://example.com/img1.jpg', {});
113 +
114 + expect(invariant).toBeCalled();
115 + });
116 +
117 + it('call invariant if wrong argument type #2', () => {
118 + Line.createImage(123);
119 +
120 + expect(invariant).toBeCalled();
121 + });
122 +});
123 +
124 +describe('#createVideo', () => {
125 + it('should return video message object', () => {
126 + expect(
127 + Line.createVideo(
128 + 'http://example.com/video.mp4',
129 + 'http://example.com/img.jpg'
130 + )
131 + ).toEqual({
132 + type: 'video',
133 + originalContentUrl: 'http://example.com/video.mp4',
134 + previewImageUrl: 'http://example.com/img.jpg',
135 + });
136 + });
137 +
138 + it('should work with object', () => {
139 + expect(
140 + Line.createVideo({
141 + originalContentUrl: 'http://example.com/video.mp4',
142 + previewImageUrl: 'http://example.com/img.jpg',
143 + })
144 + ).toEqual({
145 + type: 'video',
146 + originalContentUrl: 'http://example.com/video.mp4',
147 + previewImageUrl: 'http://example.com/img.jpg',
148 + });
149 +
150 + expect(
151 + Line.createVideo(
152 + {
153 + originalContentUrl: 'http://example.com/video.mp4',
154 + previewImageUrl: 'http://example.com/img.jpg',
155 + },
156 + quickReplyOptions
157 + )
158 + ).toEqual({
159 + type: 'video',
160 + originalContentUrl: 'http://example.com/video.mp4',
161 + previewImageUrl: 'http://example.com/img.jpg',
162 + ...quickReplyOptions,
163 + });
164 + });
165 +
166 + it('call invariant if wrong argument type', () => {
167 + Line.createVideo('http://example.com/video.mp4', {});
168 +
169 + expect(invariant).toBeCalled();
170 + });
171 +});
172 +
173 +describe('#createAudio', () => {
174 + it('should return audio message object', () => {
175 + expect(Line.createAudio('http://example.com/audio.mp3', 240000)).toEqual({
176 + type: 'audio',
177 + originalContentUrl: 'http://example.com/audio.mp3',
178 + duration: 240000,
179 + });
180 + });
181 +
182 + it('should work with object', () => {
183 + expect(
184 + Line.createAudio({
185 + originalContentUrl: 'http://example.com/audio.mp3',
186 + duration: 240000,
187 + })
188 + ).toEqual({
189 + type: 'audio',
190 + originalContentUrl: 'http://example.com/audio.mp3',
191 + duration: 240000,
192 + });
193 +
194 + expect(
195 + Line.createAudio(
196 + {
197 + originalContentUrl: 'http://example.com/audio.mp3',
198 + duration: 240000,
199 + },
200 + quickReplyOptions
201 + )
202 + ).toEqual({
203 + type: 'audio',
204 + originalContentUrl: 'http://example.com/audio.mp3',
205 + duration: 240000,
206 + ...quickReplyOptions,
207 + });
208 + });
209 +
210 + it('call invariant if wrong argument type', () => {
211 + Line.createAudio('http://example.com/audio.mp3', {});
212 +
213 + expect(invariant).toBeCalled();
214 + });
215 +});
216 +
217 +describe('#createLocation', () => {
218 + it('should return location message object', () => {
219 + expect(
220 + Line.createLocation({
221 + title: 'my location',
222 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
223 + latitude: 35.65910807942215,
224 + longitude: 139.70372892916203,
225 + })
226 + ).toEqual({
227 + type: 'location',
228 + title: 'my location',
229 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
230 + latitude: 35.65910807942215,
231 + longitude: 139.70372892916203,
232 + });
233 +
234 + expect(
235 + Line.createLocation(
236 + {
237 + title: 'my location',
238 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
239 + latitude: 35.65910807942215,
240 + longitude: 139.70372892916203,
241 + },
242 + quickReplyOptions
243 + )
244 + ).toEqual({
245 + type: 'location',
246 + title: 'my location',
247 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
248 + latitude: 35.65910807942215,
249 + longitude: 139.70372892916203,
250 + ...quickReplyOptions,
251 + });
252 + });
253 +});
254 +
255 +describe('#createSticker', () => {
256 + it('should return sticker message object', () => {
257 + expect(Line.createSticker('1', '1')).toEqual({
258 + type: 'sticker',
259 + packageId: '1',
260 + stickerId: '1',
261 + });
262 + });
263 +
264 + it('should work with object', () => {
265 + expect(
266 + Line.createSticker({
267 + packageId: '1',
268 + stickerId: '1',
269 + })
270 + ).toEqual({
271 + type: 'sticker',
272 + packageId: '1',
273 + stickerId: '1',
274 + });
275 +
276 + expect(
277 + Line.createSticker(
278 + {
279 + packageId: '1',
280 + stickerId: '1',
281 + },
282 + quickReplyOptions
283 + )
284 + ).toEqual({
285 + type: 'sticker',
286 + packageId: '1',
287 + stickerId: '1',
288 + ...quickReplyOptions,
289 + });
290 + });
291 +
292 + it('call invariant if wrong argument type', () => {
293 + Line.createSticker('1', {});
294 +
295 + expect(invariant).toBeCalled();
296 + });
297 +});
298 +
299 +describe('#createImagemap', () => {
300 + it('should return imagemap message object', () => {
301 + expect(
302 + Line.createImagemap('this is an imagemap', {
303 + baseUrl: 'https://example.com/bot/images/rm001',
304 + baseSize: {
305 + width: 1040,
306 + height: 1040,
307 + },
308 + actions: [
309 + {
310 + type: 'uri',
311 + linkUri: 'https://example.com/',
312 + area: {
313 + x: 0,
314 + y: 0,
315 + width: 520,
316 + height: 1040,
317 + },
318 + },
319 + {
320 + type: 'message',
321 + text: 'hello',
322 + area: {
323 + x: 520,
324 + y: 0,
325 + width: 520,
326 + height: 1040,
327 + },
328 + },
329 + ],
330 + })
331 + ).toEqual({
332 + type: 'imagemap',
333 + altText: 'this is an imagemap',
334 + baseUrl: 'https://example.com/bot/images/rm001',
335 + baseSize: {
336 + width: 1040,
337 + height: 1040,
338 + },
339 + actions: [
340 + {
341 + type: 'uri',
342 + linkUri: 'https://example.com/',
343 + area: {
344 + x: 0,
345 + y: 0,
346 + width: 520,
347 + height: 1040,
348 + },
349 + },
350 + {
351 + type: 'message',
352 + text: 'hello',
353 + area: {
354 + x: 520,
355 + y: 0,
356 + width: 520,
357 + height: 1040,
358 + },
359 + },
360 + ],
361 + });
362 +
363 + expect(
364 + Line.createImagemap(
365 + 'this is an imagemap',
366 + {
367 + baseUrl: 'https://example.com/bot/images/rm001',
368 + baseSize: {
369 + width: 1040,
370 + height: 1040,
371 + },
372 + actions: [
373 + {
374 + type: 'uri',
375 + linkUri: 'https://example.com/',
376 + area: {
377 + x: 0,
378 + y: 0,
379 + width: 520,
380 + height: 1040,
381 + },
382 + },
383 + {
384 + type: 'message',
385 + text: 'hello',
386 + area: {
387 + x: 520,
388 + y: 0,
389 + width: 520,
390 + height: 1040,
391 + },
392 + },
393 + ],
394 + },
395 + quickReplyOptions
396 + )
397 + ).toEqual({
398 + type: 'imagemap',
399 + altText: 'this is an imagemap',
400 + baseUrl: 'https://example.com/bot/images/rm001',
401 + baseSize: {
402 + width: 1040,
403 + height: 1040,
404 + },
405 + actions: [
406 + {
407 + type: 'uri',
408 + linkUri: 'https://example.com/',
409 + area: {
410 + x: 0,
411 + y: 0,
412 + width: 520,
413 + height: 1040,
414 + },
415 + },
416 + {
417 + type: 'message',
418 + text: 'hello',
419 + area: {
420 + x: 520,
421 + y: 0,
422 + width: 520,
423 + height: 1040,
424 + },
425 + },
426 + ],
427 + ...quickReplyOptions,
428 + });
429 + });
430 +});
431 +
432 +describe('#createImagemap', () => {
433 + it('should return imagemap message object', () => {
434 + expect(
435 + Line.createImagemap('this is an imagemap', {
436 + baseUrl: 'https://example.com/bot/images/rm001',
437 + baseSize: {
438 + width: 1040,
439 + height: 1040,
440 + },
441 + actions: [
442 + {
443 + type: 'uri',
444 + linkUri: 'https://example.com/',
445 + area: {
446 + x: 0,
447 + y: 0,
448 + width: 520,
449 + height: 1040,
450 + },
451 + },
452 + {
453 + type: 'message',
454 + text: 'hello',
455 + area: {
456 + x: 520,
457 + y: 0,
458 + width: 520,
459 + height: 1040,
460 + },
461 + },
462 + ],
463 + })
464 + ).toEqual({
465 + type: 'imagemap',
466 + altText: 'this is an imagemap',
467 + baseUrl: 'https://example.com/bot/images/rm001',
468 + baseSize: {
469 + width: 1040,
470 + height: 1040,
471 + },
472 + actions: [
473 + {
474 + type: 'uri',
475 + linkUri: 'https://example.com/',
476 + area: {
477 + x: 0,
478 + y: 0,
479 + width: 520,
480 + height: 1040,
481 + },
482 + },
483 + {
484 + type: 'message',
485 + text: 'hello',
486 + area: {
487 + x: 520,
488 + y: 0,
489 + width: 520,
490 + height: 1040,
491 + },
492 + },
493 + ],
494 + });
495 +
496 + expect(
497 + Line.createImagemap(
498 + 'this is an imagemap',
499 + {
500 + baseUrl: 'https://example.com/bot/images/rm001',
501 + baseSize: {
502 + width: 1040,
503 + height: 1040,
504 + },
505 + actions: [
506 + {
507 + type: 'uri',
508 + linkUri: 'https://example.com/',
509 + area: {
510 + x: 0,
511 + y: 0,
512 + width: 520,
513 + height: 1040,
514 + },
515 + },
516 + {
517 + type: 'message',
518 + text: 'hello',
519 + area: {
520 + x: 520,
521 + y: 0,
522 + width: 520,
523 + height: 1040,
524 + },
525 + },
526 + ],
527 + },
528 + quickReplyOptions
529 + )
530 + ).toEqual({
531 + type: 'imagemap',
532 + altText: 'this is an imagemap',
533 + baseUrl: 'https://example.com/bot/images/rm001',
534 + baseSize: {
535 + width: 1040,
536 + height: 1040,
537 + },
538 + actions: [
539 + {
540 + type: 'uri',
541 + linkUri: 'https://example.com/',
542 + area: {
543 + x: 0,
544 + y: 0,
545 + width: 520,
546 + height: 1040,
547 + },
548 + },
549 + {
550 + type: 'message',
551 + text: 'hello',
552 + area: {
553 + x: 520,
554 + y: 0,
555 + width: 520,
556 + height: 1040,
557 + },
558 + },
559 + ],
560 + ...quickReplyOptions,
561 + });
562 + });
563 +});
564 +
565 +describe('#createTemplate', () => {
566 + it('should return template message object', () => {
567 + expect(
568 + Line.createTemplate('this is a buttons template', {
569 + type: 'buttons',
570 + text: 'Are you sure?',
571 + actions: [
572 + {
573 + type: 'message',
574 + label: 'Yes',
575 + text: 'yes',
576 + },
577 + {
578 + type: 'message',
579 + label: 'No',
580 + text: 'no',
581 + },
582 + ],
583 + })
584 + ).toEqual({
585 + type: 'template',
586 + altText: 'this is a buttons template',
587 + template: {
588 + type: 'buttons',
589 + text: 'Are you sure?',
590 + actions: [
591 + {
592 + type: 'message',
593 + label: 'Yes',
594 + text: 'yes',
595 + },
596 + {
597 + type: 'message',
598 + label: 'No',
599 + text: 'no',
600 + },
601 + ],
602 + },
603 + });
604 +
605 + expect(
606 + Line.createTemplate(
607 + 'this is a buttons template',
608 + {
609 + type: 'buttons',
610 + text: 'Are you sure?',
611 + actions: [
612 + {
613 + type: 'message',
614 + label: 'Yes',
615 + text: 'yes',
616 + },
617 + {
618 + type: 'message',
619 + label: 'No',
620 + text: 'no',
621 + },
622 + ],
623 + },
624 + quickReplyOptions
625 + )
626 + ).toEqual({
627 + type: 'template',
628 + altText: 'this is a buttons template',
629 + template: {
630 + type: 'buttons',
631 + text: 'Are you sure?',
632 + actions: [
633 + {
634 + type: 'message',
635 + label: 'Yes',
636 + text: 'yes',
637 + },
638 + {
639 + type: 'message',
640 + label: 'No',
641 + text: 'no',
642 + },
643 + ],
644 + },
645 + ...quickReplyOptions,
646 + });
647 + });
648 +});
649 +
650 +describe('#createTemplate', () => {
651 + it('should return template message object', () => {
652 + expect(
653 + Line.createTemplate('this is a confirm template', {
654 + type: 'confirm',
655 + text: 'Are you sure?',
656 + actions: [
657 + {
658 + type: 'message',
659 + label: 'Yes',
660 + text: 'yes',
661 + },
662 + {
663 + type: 'message',
664 + label: 'No',
665 + text: 'no',
666 + },
667 + ],
668 + })
669 + ).toEqual({
670 + type: 'template',
671 + altText: 'this is a confirm template',
672 + template: {
673 + type: 'confirm',
674 + text: 'Are you sure?',
675 + actions: [
676 + {
677 + type: 'message',
678 + label: 'Yes',
679 + text: 'yes',
680 + },
681 + {
682 + type: 'message',
683 + label: 'No',
684 + text: 'no',
685 + },
686 + ],
687 + },
688 + });
689 +
690 + expect(
691 + Line.createTemplate(
692 + 'this is a confirm template',
693 + {
694 + type: 'confirm',
695 + text: 'Are you sure?',
696 + actions: [
697 + {
698 + type: 'message',
699 + label: 'Yes',
700 + text: 'yes',
701 + },
702 + {
703 + type: 'message',
704 + label: 'No',
705 + text: 'no',
706 + },
707 + ],
708 + },
709 + quickReplyOptions
710 + )
711 + ).toEqual({
712 + type: 'template',
713 + altText: 'this is a confirm template',
714 + template: {
715 + type: 'confirm',
716 + text: 'Are you sure?',
717 + actions: [
718 + {
719 + type: 'message',
720 + label: 'Yes',
721 + text: 'yes',
722 + },
723 + {
724 + type: 'message',
725 + label: 'No',
726 + text: 'no',
727 + },
728 + ],
729 + },
730 + ...quickReplyOptions,
731 + });
732 + });
733 +});
734 +
735 +describe('#createButtonTemplate', () => {
736 + it('should return buttons template message object', () => {
737 + expect(
738 + Line.createButtonTemplate('this is a buttons template', {
739 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
740 + title: 'Menu',
741 + text: 'Please select',
742 + defaultAction: {
743 + type: 'uri',
744 + label: 'View detail',
745 + uri: 'http://example.com/page/123',
746 + },
747 + actions: [
748 + {
749 + type: 'postback',
750 + label: 'Buy',
751 + data: 'action=buy&itemid=123',
752 + },
753 + {
754 + type: 'postback',
755 + label: 'Add to cart',
756 + data: 'action=add&itemid=123',
757 + },
758 + {
759 + type: 'uri',
760 + label: 'View detail',
761 + uri: 'http://example.com/page/123',
762 + },
763 + ],
764 + })
765 + ).toEqual({
766 + type: 'template',
767 + altText: 'this is a buttons template',
768 + template: {
769 + type: 'buttons',
770 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
771 + title: 'Menu',
772 + text: 'Please select',
773 + defaultAction: {
774 + type: 'uri',
775 + label: 'View detail',
776 + uri: 'http://example.com/page/123',
777 + },
778 + actions: [
779 + {
780 + type: 'postback',
781 + label: 'Buy',
782 + data: 'action=buy&itemid=123',
783 + },
784 + {
785 + type: 'postback',
786 + label: 'Add to cart',
787 + data: 'action=add&itemid=123',
788 + },
789 + {
790 + type: 'uri',
791 + label: 'View detail',
792 + uri: 'http://example.com/page/123',
793 + },
794 + ],
795 + },
796 + });
797 +
798 + expect(
799 + Line.createButtonTemplate(
800 + 'this is a buttons template',
801 + {
802 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
803 + title: 'Menu',
804 + text: 'Please select',
805 + actions: [
806 + {
807 + type: 'postback',
808 + label: 'Buy',
809 + data: 'action=buy&itemid=123',
810 + },
811 + {
812 + type: 'postback',
813 + label: 'Add to cart',
814 + data: 'action=add&itemid=123',
815 + },
816 + {
817 + type: 'uri',
818 + label: 'View detail',
819 + uri: 'http://example.com/page/123',
820 + },
821 + ],
822 + },
823 + quickReplyOptions
824 + )
825 + ).toEqual({
826 + type: 'template',
827 + altText: 'this is a buttons template',
828 + template: {
829 + type: 'buttons',
830 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
831 + title: 'Menu',
832 + text: 'Please select',
833 + actions: [
834 + {
835 + type: 'postback',
836 + label: 'Buy',
837 + data: 'action=buy&itemid=123',
838 + },
839 + {
840 + type: 'postback',
841 + label: 'Add to cart',
842 + data: 'action=add&itemid=123',
843 + },
844 + {
845 + type: 'uri',
846 + label: 'View detail',
847 + uri: 'http://example.com/page/123',
848 + },
849 + ],
850 + },
851 + ...quickReplyOptions,
852 + });
853 + });
854 +
855 + it('should support createButtonsTemplate alias', () => {
856 + expect(
857 + Line.createButtonsTemplate('this is a buttons template', {
858 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
859 + title: 'Menu',
860 + text: 'Please select',
861 + actions: [
862 + {
863 + type: 'postback',
864 + label: 'Buy',
865 + data: 'action=buy&itemid=123',
866 + },
867 + {
868 + type: 'postback',
869 + label: 'Add to cart',
870 + data: 'action=add&itemid=123',
871 + },
872 + {
873 + type: 'uri',
874 + label: 'View detail',
875 + uri: 'http://example.com/page/123',
876 + },
877 + ],
878 + })
879 + ).toEqual({
880 + type: 'template',
881 + altText: 'this is a buttons template',
882 + template: {
883 + type: 'buttons',
884 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
885 + title: 'Menu',
886 + text: 'Please select',
887 + actions: [
888 + {
889 + type: 'postback',
890 + label: 'Buy',
891 + data: 'action=buy&itemid=123',
892 + },
893 + {
894 + type: 'postback',
895 + label: 'Add to cart',
896 + data: 'action=add&itemid=123',
897 + },
898 + {
899 + type: 'uri',
900 + label: 'View detail',
901 + uri: 'http://example.com/page/123',
902 + },
903 + ],
904 + },
905 + });
906 + });
907 +});
908 +
909 +describe('#createConfirmTemplate', () => {
910 + it('should return confirm template message object', () => {
911 + expect(
912 + Line.createConfirmTemplate('this is a confirm template', {
913 + text: 'Are you sure?',
914 + actions: [
915 + {
916 + type: 'message',
917 + label: 'Yes',
918 + text: 'yes',
919 + },
920 + {
921 + type: 'message',
922 + label: 'No',
923 + text: 'no',
924 + },
925 + ],
926 + })
927 + ).toEqual({
928 + type: 'template',
929 + altText: 'this is a confirm template',
930 + template: {
931 + type: 'confirm',
932 + text: 'Are you sure?',
933 + actions: [
934 + {
935 + type: 'message',
936 + label: 'Yes',
937 + text: 'yes',
938 + },
939 + {
940 + type: 'message',
941 + label: 'No',
942 + text: 'no',
943 + },
944 + ],
945 + },
946 + });
947 +
948 + expect(
949 + Line.createConfirmTemplate(
950 + 'this is a confirm template',
951 + {
952 + text: 'Are you sure?',
953 + actions: [
954 + {
955 + type: 'message',
956 + label: 'Yes',
957 + text: 'yes',
958 + },
959 + {
960 + type: 'message',
961 + label: 'No',
962 + text: 'no',
963 + },
964 + ],
965 + },
966 + quickReplyOptions
967 + )
968 + ).toEqual({
969 + type: 'template',
970 + altText: 'this is a confirm template',
971 + template: {
972 + type: 'confirm',
973 + text: 'Are you sure?',
974 + actions: [
975 + {
976 + type: 'message',
977 + label: 'Yes',
978 + text: 'yes',
979 + },
980 + {
981 + type: 'message',
982 + label: 'No',
983 + text: 'no',
984 + },
985 + ],
986 + },
987 + ...quickReplyOptions,
988 + });
989 + });
990 +});
991 +
992 +describe('#createCarouselTemplate', () => {
993 + it('should return carousel template message object', () => {
994 + expect(
995 + Line.createCarouselTemplate('this is a carousel template', [
996 + {
997 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
998 + title: 'this is menu',
999 + text: 'description',
1000 + actions: [
1001 + {
1002 + type: 'postback',
1003 + label: 'Buy',
1004 + data: 'action=buy&itemid=111',
1005 + },
1006 + {
1007 + type: 'postback',
1008 + label: 'Add to cart',
1009 + data: 'action=add&itemid=111',
1010 + },
1011 + {
1012 + type: 'uri',
1013 + label: 'View detail',
1014 + uri: 'http://example.com/page/111',
1015 + },
1016 + ],
1017 + },
1018 + {
1019 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1020 + title: 'this is menu',
1021 + text: 'description',
1022 + actions: [
1023 + {
1024 + type: 'postback',
1025 + label: 'Buy',
1026 + data: 'action=buy&itemid=222',
1027 + },
1028 + {
1029 + type: 'postback',
1030 + label: 'Add to cart',
1031 + data: 'action=add&itemid=222',
1032 + },
1033 + {
1034 + type: 'uri',
1035 + label: 'View detail',
1036 + uri: 'http://example.com/page/222',
1037 + },
1038 + ],
1039 + },
1040 + ])
1041 + ).toEqual({
1042 + type: 'template',
1043 + altText: 'this is a carousel template',
1044 + template: {
1045 + type: 'carousel',
1046 + columns: [
1047 + {
1048 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1049 + title: 'this is menu',
1050 + text: 'description',
1051 + actions: [
1052 + {
1053 + type: 'postback',
1054 + label: 'Buy',
1055 + data: 'action=buy&itemid=111',
1056 + },
1057 + {
1058 + type: 'postback',
1059 + label: 'Add to cart',
1060 + data: 'action=add&itemid=111',
1061 + },
1062 + {
1063 + type: 'uri',
1064 + label: 'View detail',
1065 + uri: 'http://example.com/page/111',
1066 + },
1067 + ],
1068 + },
1069 + {
1070 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1071 + title: 'this is menu',
1072 + text: 'description',
1073 + actions: [
1074 + {
1075 + type: 'postback',
1076 + label: 'Buy',
1077 + data: 'action=buy&itemid=222',
1078 + },
1079 + {
1080 + type: 'postback',
1081 + label: 'Add to cart',
1082 + data: 'action=add&itemid=222',
1083 + },
1084 + {
1085 + type: 'uri',
1086 + label: 'View detail',
1087 + uri: 'http://example.com/page/222',
1088 + },
1089 + ],
1090 + },
1091 + ],
1092 + },
1093 + });
1094 +
1095 + expect(
1096 + Line.createCarouselTemplate(
1097 + 'this is a carousel template',
1098 + [
1099 + {
1100 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1101 + title: 'this is menu',
1102 + text: 'description',
1103 + actions: [
1104 + {
1105 + type: 'postback',
1106 + label: 'Buy',
1107 + data: 'action=buy&itemid=111',
1108 + },
1109 + {
1110 + type: 'postback',
1111 + label: 'Add to cart',
1112 + data: 'action=add&itemid=111',
1113 + },
1114 + {
1115 + type: 'uri',
1116 + label: 'View detail',
1117 + uri: 'http://example.com/page/111',
1118 + },
1119 + ],
1120 + },
1121 + {
1122 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1123 + title: 'this is menu',
1124 + text: 'description',
1125 + actions: [
1126 + {
1127 + type: 'postback',
1128 + label: 'Buy',
1129 + data: 'action=buy&itemid=222',
1130 + },
1131 + {
1132 + type: 'postback',
1133 + label: 'Add to cart',
1134 + data: 'action=add&itemid=222',
1135 + },
1136 + {
1137 + type: 'uri',
1138 + label: 'View detail',
1139 + uri: 'http://example.com/page/222',
1140 + },
1141 + ],
1142 + },
1143 + ],
1144 + quickReplyOptions
1145 + )
1146 + ).toEqual({
1147 + type: 'template',
1148 + altText: 'this is a carousel template',
1149 + template: {
1150 + type: 'carousel',
1151 + columns: [
1152 + {
1153 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1154 + title: 'this is menu',
1155 + text: 'description',
1156 + actions: [
1157 + {
1158 + type: 'postback',
1159 + label: 'Buy',
1160 + data: 'action=buy&itemid=111',
1161 + },
1162 + {
1163 + type: 'postback',
1164 + label: 'Add to cart',
1165 + data: 'action=add&itemid=111',
1166 + },
1167 + {
1168 + type: 'uri',
1169 + label: 'View detail',
1170 + uri: 'http://example.com/page/111',
1171 + },
1172 + ],
1173 + },
1174 + {
1175 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1176 + title: 'this is menu',
1177 + text: 'description',
1178 + actions: [
1179 + {
1180 + type: 'postback',
1181 + label: 'Buy',
1182 + data: 'action=buy&itemid=222',
1183 + },
1184 + {
1185 + type: 'postback',
1186 + label: 'Add to cart',
1187 + data: 'action=add&itemid=222',
1188 + },
1189 + {
1190 + type: 'uri',
1191 + label: 'View detail',
1192 + uri: 'http://example.com/page/222',
1193 + },
1194 + ],
1195 + },
1196 + ],
1197 + },
1198 + ...quickReplyOptions,
1199 + });
1200 + });
1201 +});
1202 +
1203 +describe('#createImageCarouselTemplate', () => {
1204 + it('should return image carousel template message object', () => {
1205 + expect(
1206 + Line.createImageCarouselTemplate('this is a image carousel template', [
1207 + {
1208 + imageUrl: 'https://example.com/bot/images/item1.jpg',
1209 + action: {
1210 + type: 'postback',
1211 + label: 'Buy',
1212 + data: 'action=buy&itemid=111',
1213 + },
1214 + },
1215 + {
1216 + imageUrl: 'https://example.com/bot/images/item2.jpg',
1217 + action: {
1218 + type: 'message',
1219 + label: 'Yes',
1220 + text: 'yes',
1221 + },
1222 + },
1223 + {
1224 + imageUrl: 'https://example.com/bot/images/item3.jpg',
1225 + action: {
1226 + type: 'uri',
1227 + label: 'View detail',
1228 + uri: 'http://example.com/page/222',
1229 + },
1230 + },
1231 + ])
1232 + ).toEqual({
1233 + type: 'template',
1234 + altText: 'this is a image carousel template',
1235 + template: {
1236 + type: 'image_carousel',
1237 + columns: [
1238 + {
1239 + imageUrl: 'https://example.com/bot/images/item1.jpg',
1240 + action: {
1241 + type: 'postback',
1242 + label: 'Buy',
1243 + data: 'action=buy&itemid=111',
1244 + },
1245 + },
1246 + {
1247 + imageUrl: 'https://example.com/bot/images/item2.jpg',
1248 + action: {
1249 + type: 'message',
1250 + label: 'Yes',
1251 + text: 'yes',
1252 + },
1253 + },
1254 + {
1255 + imageUrl: 'https://example.com/bot/images/item3.jpg',
1256 + action: {
1257 + type: 'uri',
1258 + label: 'View detail',
1259 + uri: 'http://example.com/page/222',
1260 + },
1261 + },
1262 + ],
1263 + },
1264 + });
1265 +
1266 + expect(
1267 + Line.createImageCarouselTemplate(
1268 + 'this is a image carousel template',
1269 + [
1270 + {
1271 + imageUrl: 'https://example.com/bot/images/item1.jpg',
1272 + action: {
1273 + type: 'postback',
1274 + label: 'Buy',
1275 + data: 'action=buy&itemid=111',
1276 + },
1277 + },
1278 + {
1279 + imageUrl: 'https://example.com/bot/images/item2.jpg',
1280 + action: {
1281 + type: 'message',
1282 + label: 'Yes',
1283 + text: 'yes',
1284 + },
1285 + },
1286 + {
1287 + imageUrl: 'https://example.com/bot/images/item3.jpg',
1288 + action: {
1289 + type: 'uri',
1290 + label: 'View detail',
1291 + uri: 'http://example.com/page/222',
1292 + },
1293 + },
1294 + ],
1295 + quickReplyOptions
1296 + )
1297 + ).toEqual({
1298 + type: 'template',
1299 + altText: 'this is a image carousel template',
1300 + template: {
1301 + type: 'image_carousel',
1302 + columns: [
1303 + {
1304 + imageUrl: 'https://example.com/bot/images/item1.jpg',
1305 + action: {
1306 + type: 'postback',
1307 + label: 'Buy',
1308 + data: 'action=buy&itemid=111',
1309 + },
1310 + },
1311 + {
1312 + imageUrl: 'https://example.com/bot/images/item2.jpg',
1313 + action: {
1314 + type: 'message',
1315 + label: 'Yes',
1316 + text: 'yes',
1317 + },
1318 + },
1319 + {
1320 + imageUrl: 'https://example.com/bot/images/item3.jpg',
1321 + action: {
1322 + type: 'uri',
1323 + label: 'View detail',
1324 + uri: 'http://example.com/page/222',
1325 + },
1326 + },
1327 + ],
1328 + },
1329 + ...quickReplyOptions,
1330 + });
1331 + });
1332 +});
1333 +
1334 +describe('#createFlex', () => {
1335 + it('should return flex message object', () => {
1336 + expect(
1337 + Line.createFlex('this is a flex message', {
1338 + type: 'bubble',
1339 + header: {
1340 + type: 'box',
1341 + layout: 'vertical',
1342 + contents: [
1343 + {
1344 + type: 'text',
1345 + text: 'Header text',
1346 + },
1347 + ],
1348 + },
1349 + hero: {
1350 + type: 'image',
1351 + url: 'https://example.com/flex/images/image.jpg',
1352 + },
1353 + body: {
1354 + type: 'box',
1355 + layout: 'vertical',
1356 + contents: [
1357 + {
1358 + type: 'text',
1359 + text: 'Body text',
1360 + },
1361 + ],
1362 + },
1363 + footer: {
1364 + type: 'box',
1365 + layout: 'vertical',
1366 + contents: [
1367 + {
1368 + type: 'text',
1369 + text: 'Footer text',
1370 + },
1371 + ],
1372 + },
1373 + styles: {
1374 + comment: 'See the example of a bubble style object',
1375 + },
1376 + })
1377 + ).toEqual({
1378 + type: 'flex',
1379 + altText: 'this is a flex message',
1380 + contents: {
1381 + type: 'bubble',
1382 + header: {
1383 + type: 'box',
1384 + layout: 'vertical',
1385 + contents: [
1386 + {
1387 + type: 'text',
1388 + text: 'Header text',
1389 + },
1390 + ],
1391 + },
1392 + hero: {
1393 + type: 'image',
1394 + url: 'https://example.com/flex/images/image.jpg',
1395 + },
1396 + body: {
1397 + type: 'box',
1398 + layout: 'vertical',
1399 + contents: [
1400 + {
1401 + type: 'text',
1402 + text: 'Body text',
1403 + },
1404 + ],
1405 + },
1406 + footer: {
1407 + type: 'box',
1408 + layout: 'vertical',
1409 + contents: [
1410 + {
1411 + type: 'text',
1412 + text: 'Footer text',
1413 + },
1414 + ],
1415 + },
1416 + styles: {
1417 + comment: 'See the example of a bubble style object',
1418 + },
1419 + },
1420 + });
1421 +
1422 + expect(
1423 + Line.createFlex(
1424 + 'this is a flex message',
1425 + {
1426 + type: 'bubble',
1427 + header: {
1428 + type: 'box',
1429 + layout: 'vertical',
1430 + contents: [
1431 + {
1432 + type: 'text',
1433 + text: 'Header text',
1434 + },
1435 + ],
1436 + },
1437 + hero: {
1438 + type: 'image',
1439 + url: 'https://example.com/flex/images/image.jpg',
1440 + },
1441 + body: {
1442 + type: 'box',
1443 + layout: 'vertical',
1444 + contents: [
1445 + {
1446 + type: 'text',
1447 + text: 'Body text',
1448 + },
1449 + ],
1450 + },
1451 + footer: {
1452 + type: 'box',
1453 + layout: 'vertical',
1454 + contents: [
1455 + {
1456 + type: 'text',
1457 + text: 'Footer text',
1458 + },
1459 + ],
1460 + },
1461 + styles: {
1462 + comment: 'See the example of a bubble style object',
1463 + },
1464 + },
1465 + quickReplyOptions
1466 + )
1467 + ).toEqual({
1468 + type: 'flex',
1469 + altText: 'this is a flex message',
1470 + contents: {
1471 + type: 'bubble',
1472 + header: {
1473 + type: 'box',
1474 + layout: 'vertical',
1475 + contents: [
1476 + {
1477 + type: 'text',
1478 + text: 'Header text',
1479 + },
1480 + ],
1481 + },
1482 + hero: {
1483 + type: 'image',
1484 + url: 'https://example.com/flex/images/image.jpg',
1485 + },
1486 + body: {
1487 + type: 'box',
1488 + layout: 'vertical',
1489 + contents: [
1490 + {
1491 + type: 'text',
1492 + text: 'Body text',
1493 + },
1494 + ],
1495 + },
1496 + footer: {
1497 + type: 'box',
1498 + layout: 'vertical',
1499 + contents: [
1500 + {
1501 + type: 'text',
1502 + text: 'Footer text',
1503 + },
1504 + ],
1505 + },
1506 + styles: {
1507 + comment: 'See the example of a bubble style object',
1508 + },
1509 + },
1510 + ...quickReplyOptions,
1511 + });
1512 + });
1513 +});
1 +import MockAdapter from 'axios-mock-adapter';
2 +
3 +import LineClient from '../LineClient';
4 +
5 +const ACCESS_TOKEN = '1234567890';
6 +const CHANNEL_SECRET = 'so-secret';
7 +
8 +describe('connect', () => {
9 + let axios;
10 + let _create;
11 + beforeEach(() => {
12 + axios = require('axios'); // eslint-disable-line global-require
13 + _create = axios.create;
14 + });
15 +
16 + afterEach(() => {
17 + axios.create = _create;
18 + });
19 +
20 + describe('create axios with Line API', () => {
21 + it('with args', () => {
22 + axios.create = jest.fn().mockReturnValue({
23 + interceptors: {
24 + request: {
25 + use: jest.fn(),
26 + },
27 + },
28 + });
29 + LineClient.connect(ACCESS_TOKEN, CHANNEL_SECRET);
30 +
31 + expect(axios.create).toBeCalledWith({
32 + baseURL: 'https://api.line.me/',
33 + headers: {
34 + Authorization: `Bearer ${ACCESS_TOKEN}`,
35 + 'Content-Type': 'application/json',
36 + },
37 + });
38 + });
39 +
40 + it('with config', () => {
41 + axios.create = jest.fn().mockReturnValue({
42 + interceptors: {
43 + request: {
44 + use: jest.fn(),
45 + },
46 + },
47 + });
48 + LineClient.connect({
49 + accessToken: ACCESS_TOKEN,
50 + channelSecret: CHANNEL_SECRET,
51 + });
52 +
53 + expect(axios.create).toBeCalledWith({
54 + baseURL: 'https://api.line.me/',
55 + headers: {
56 + Authorization: `Bearer ${ACCESS_TOKEN}`,
57 + 'Content-Type': 'application/json',
58 + },
59 + });
60 + });
61 + });
62 +
63 + it('support origin', () => {
64 + axios.create = jest.fn().mockReturnValue({
65 + interceptors: {
66 + request: {
67 + use: jest.fn(),
68 + },
69 + },
70 + });
71 + LineClient.connect({
72 + accessToken: ACCESS_TOKEN,
73 + channelSecret: CHANNEL_SECRET,
74 + origin: 'https://mydummytestserver.com',
75 + });
76 +
77 + expect(axios.create).toBeCalledWith({
78 + baseURL: 'https://mydummytestserver.com/',
79 + headers: {
80 + Authorization: `Bearer ${ACCESS_TOKEN}`,
81 + 'Content-Type': 'application/json',
82 + },
83 + });
84 + });
85 +});
86 +
87 +describe('constructor', () => {
88 + let axios;
89 + let _create;
90 + beforeEach(() => {
91 + axios = require('axios'); // eslint-disable-line global-require
92 + _create = axios.create;
93 + });
94 +
95 + afterEach(() => {
96 + axios.create = _create;
97 + });
98 +
99 + describe('create axios with Line API', () => {
100 + it('with args', () => {
101 + axios.create = jest.fn().mockReturnValue({
102 + interceptors: {
103 + request: {
104 + use: jest.fn(),
105 + },
106 + },
107 + });
108 + new LineClient(ACCESS_TOKEN, CHANNEL_SECRET); // eslint-disable-line no-new
109 +
110 + expect(axios.create).toBeCalledWith({
111 + baseURL: 'https://api.line.me/',
112 + headers: {
113 + Authorization: `Bearer ${ACCESS_TOKEN}`,
114 + 'Content-Type': 'application/json',
115 + },
116 + });
117 + });
118 +
119 + it('with config', () => {
120 + axios.create = jest.fn().mockReturnValue({
121 + interceptors: {
122 + request: {
123 + use: jest.fn(),
124 + },
125 + },
126 + });
127 + // eslint-disable-next-line no-new
128 + new LineClient({
129 + accessToken: ACCESS_TOKEN,
130 + channelSecret: CHANNEL_SECRET,
131 + });
132 +
133 + expect(axios.create).toBeCalledWith({
134 + baseURL: 'https://api.line.me/',
135 + headers: {
136 + Authorization: `Bearer ${ACCESS_TOKEN}`,
137 + 'Content-Type': 'application/json',
138 + },
139 + });
140 + });
141 + });
142 +
143 + it('support origin', () => {
144 + axios.create = jest.fn().mockReturnValue({
145 + interceptors: {
146 + request: {
147 + use: jest.fn(),
148 + },
149 + },
150 + });
151 + // eslint-disable-next-line no-new
152 + new LineClient({
153 + accessToken: ACCESS_TOKEN,
154 + channelSecret: CHANNEL_SECRET,
155 + origin: 'https://mydummytestserver.com',
156 + });
157 +
158 + expect(axios.create).toBeCalledWith({
159 + baseURL: 'https://mydummytestserver.com/',
160 + headers: {
161 + Authorization: `Bearer ${ACCESS_TOKEN}`,
162 + 'Content-Type': 'application/json',
163 + },
164 + });
165 + });
166 +});
167 +
168 +describe('#axios', () => {
169 + it('should return underlying http client', () => {
170 + let client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
171 + expect(client.axios.get).toBeDefined();
172 + expect(client.axios.post).toBeDefined();
173 + expect(client.axios.put).toBeDefined();
174 + expect(client.axios.delete).toBeDefined();
175 +
176 + client = new LineClient({
177 + accessToken: ACCESS_TOKEN,
178 + channelSecret: CHANNEL_SECRET,
179 + });
180 + expect(client.axios.get).toBeDefined();
181 + expect(client.axios.post).toBeDefined();
182 + expect(client.axios.put).toBeDefined();
183 + expect(client.axios.delete).toBeDefined();
184 + });
185 +});
186 +
187 +describe('#accessToken', () => {
188 + it('should return underlying access token', () => {
189 + let client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
190 + expect(client.accessToken).toBe(ACCESS_TOKEN);
191 +
192 + client = new LineClient({
193 + accessToken: ACCESS_TOKEN,
194 + channelSecret: CHANNEL_SECRET,
195 + });
196 + expect(client.accessToken).toBe(ACCESS_TOKEN);
197 + });
198 +});
199 +
200 +describe('#onRequest', () => {
201 + it('should call onRequest when calling any API', async () => {
202 + const onRequest = jest.fn();
203 + const client = new LineClient({
204 + accessToken: ACCESS_TOKEN,
205 + channelSecret: CHANNEL_SECRET,
206 + onRequest,
207 + });
208 +
209 + const mock = new MockAdapter(client.axios);
210 +
211 + mock.onPost('/path').reply(200, {});
212 +
213 + await client.axios.post('/path', { x: 1 });
214 +
215 + expect(onRequest).toBeCalledWith({
216 + method: 'post',
217 + url: 'https://api.line.me/path',
218 + body: {
219 + x: 1,
220 + },
221 + headers: {
222 + Authorization: 'Bearer 1234567890',
223 + 'Content-Type': 'application/json',
224 + Accept: 'application/json, text/plain, */*',
225 + },
226 + });
227 + });
228 +});
229 +
230 +describe('Client instance', () => {
231 + it('prototype should be defined', () => {
232 + const sendTypes = ['reply', 'push', 'multicast'];
233 + const messageTypes = [
234 + 'Text',
235 + 'Image',
236 + 'Video',
237 + 'Audio',
238 + 'Location',
239 + 'Sticker',
240 + 'Imagemap',
241 + 'Template',
242 + 'ButtonTemplate',
243 + 'ConfirmTemplate',
244 + 'CarouselTemplate',
245 + 'ImageCarouselTemplate',
246 + ];
247 +
248 + let client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
249 +
250 + sendTypes.forEach(sendType => {
251 + messageTypes.forEach(messageType => {
252 + expect(client[`${sendType}${messageType}`]).toBeDefined();
253 + });
254 + });
255 +
256 + client = new LineClient({
257 + accessToken: ACCESS_TOKEN,
258 + channelSecret: CHANNEL_SECRET,
259 + });
260 +
261 + sendTypes.forEach(sendType => {
262 + messageTypes.forEach(messageType => {
263 + expect(client[`${sendType}${messageType}`]).toBeDefined();
264 + });
265 + });
266 + });
267 +});
1 +import MockAdapter from 'axios-mock-adapter';
2 +
3 +import LineClient from '../LineClient';
4 +
5 +const CUSTOM_ACCESS_TOKEN = '555555555';
6 +const ACCESS_TOKEN = '1234567890';
7 +const CHANNEL_SECRET = 'so-secret';
8 +
9 +const createMock = ({ customAccessToken } = {}) => {
10 + const client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
11 + const mock = new MockAdapter(client.axios);
12 + const headers = {
13 + Accept: 'application/json, text/plain, */*',
14 + 'Content-Type': 'application/json',
15 + Authorization: `Bearer ${customAccessToken || ACCESS_TOKEN}`,
16 + };
17 + return { client, mock, headers };
18 +};
19 +
20 +describe('LINE Front-end Framework', () => {
21 + describe('#getLiffAppList', () => {
22 + it('should call api', async () => {
23 + expect.assertions(4);
24 +
25 + const { client, mock, headers } = createMock();
26 +
27 + const reply = {
28 + apps: [
29 + {
30 + liffId: 'liff-12345',
31 + view: {
32 + type: 'full',
33 + url: 'https://example.com/myservice',
34 + },
35 + },
36 + {
37 + liffId: 'liff-67890',
38 + view: {
39 + type: 'tall',
40 + url: 'https://example.com/myservice2',
41 + },
42 + },
43 + ],
44 + };
45 +
46 + mock.onGet().reply(config => {
47 + expect(config.url).toEqual('https://api.line.me/liff/v1/apps');
48 + expect(config.data).toEqual(undefined);
49 + expect(config.headers).toEqual(headers);
50 + return [200, reply];
51 + });
52 +
53 + const res = await client.getLiffAppList();
54 +
55 + expect(res).toEqual([
56 + {
57 + liffId: 'liff-12345',
58 + view: {
59 + type: 'full',
60 + url: 'https://example.com/myservice',
61 + },
62 + },
63 + {
64 + liffId: 'liff-67890',
65 + view: {
66 + type: 'tall',
67 + url: 'https://example.com/myservice2',
68 + },
69 + },
70 + ]);
71 + });
72 +
73 + it('should work with custom access token', async () => {
74 + expect.assertions(4);
75 +
76 + const { client, mock, headers } = createMock({
77 + customAccessToken: CUSTOM_ACCESS_TOKEN,
78 + });
79 +
80 + const reply = {
81 + apps: [
82 + {
83 + liffId: 'liff-12345',
84 + view: {
85 + type: 'full',
86 + url: 'https://example.com/myservice',
87 + },
88 + },
89 + {
90 + liffId: 'liff-67890',
91 + view: {
92 + type: 'tall',
93 + url: 'https://example.com/myservice2',
94 + },
95 + },
96 + ],
97 + };
98 +
99 + mock.onGet().reply(config => {
100 + expect(config.url).toEqual('https://api.line.me/liff/v1/apps');
101 + expect(config.data).toEqual(undefined);
102 + expect(config.headers).toEqual(headers);
103 + return [200, reply];
104 + });
105 +
106 + const res = await client.getLiffAppList({
107 + accessToken: CUSTOM_ACCESS_TOKEN,
108 + });
109 +
110 + expect(res).toEqual([
111 + {
112 + liffId: 'liff-12345',
113 + view: {
114 + type: 'full',
115 + url: 'https://example.com/myservice',
116 + },
117 + },
118 + {
119 + liffId: 'liff-67890',
120 + view: {
121 + type: 'tall',
122 + url: 'https://example.com/myservice2',
123 + },
124 + },
125 + ]);
126 + });
127 + });
128 +
129 + describe('#createLiffApp', () => {
130 + it('should call api', async () => {
131 + expect.assertions(4);
132 +
133 + const { client, mock, headers } = createMock();
134 +
135 + const reply = {
136 + liffId: 'liff-12345',
137 + };
138 +
139 + mock.onPost().reply(config => {
140 + expect(config.url).toEqual('https://api.line.me/liff/v1/apps');
141 + expect(JSON.parse(config.data)).toEqual({
142 + type: 'tall',
143 + url: 'https://example.com/myservice',
144 + });
145 + expect(config.headers).toEqual(headers);
146 + return [200, reply];
147 + });
148 +
149 + const res = await client.createLiffApp({
150 + type: 'tall',
151 + url: 'https://example.com/myservice',
152 + });
153 +
154 + expect(res).toEqual({
155 + liffId: 'liff-12345',
156 + });
157 + });
158 +
159 + it('should work with custom access token', async () => {
160 + const { client, mock, headers } = createMock({
161 + customAccessToken: CUSTOM_ACCESS_TOKEN,
162 + });
163 +
164 + const reply = {
165 + liffId: 'liff-12345',
166 + };
167 +
168 + mock.onPost().reply(config => {
169 + expect(config.url).toEqual('https://api.line.me/liff/v1/apps');
170 + expect(JSON.parse(config.data)).toEqual({
171 + type: 'tall',
172 + url: 'https://example.com/myservice',
173 + });
174 + expect(config.headers).toEqual(headers);
175 + return [200, reply];
176 + });
177 +
178 + const res = await client.createLiffApp(
179 + {
180 + type: 'tall',
181 + url: 'https://example.com/myservice',
182 + },
183 + { accessToken: CUSTOM_ACCESS_TOKEN }
184 + );
185 +
186 + expect(res).toEqual({
187 + liffId: 'liff-12345',
188 + });
189 + });
190 + });
191 +
192 + describe('#updateLiffApp', () => {
193 + it('should call api', async () => {
194 + expect.assertions(4);
195 +
196 + const { client, mock, headers } = createMock();
197 +
198 + const reply = {};
199 +
200 + mock.onPut().reply(config => {
201 + expect(config.url).toEqual(
202 + 'https://api.line.me/liff/v1/apps/liff-12345/view'
203 + );
204 + expect(JSON.parse(config.data)).toEqual({
205 + type: 'tall',
206 + url: 'https://example.com/myservice',
207 + });
208 + expect(config.headers).toEqual(headers);
209 + return [200, reply];
210 + });
211 +
212 + const res = await client.updateLiffApp('liff-12345', {
213 + type: 'tall',
214 + url: 'https://example.com/myservice',
215 + });
216 +
217 + expect(res).toEqual(reply);
218 + });
219 +
220 + it('should work with custom access token', async () => {
221 + expect.assertions(4);
222 +
223 + const { client, mock, headers } = createMock({
224 + customAccessToken: CUSTOM_ACCESS_TOKEN,
225 + });
226 +
227 + const reply = {};
228 +
229 + mock.onPut().reply(config => {
230 + expect(config.url).toEqual(
231 + 'https://api.line.me/liff/v1/apps/liff-12345/view'
232 + );
233 + expect(JSON.parse(config.data)).toEqual({
234 + type: 'tall',
235 + url: 'https://example.com/myservice',
236 + });
237 + expect(config.headers).toEqual(headers);
238 + return [200, reply];
239 + });
240 +
241 + const res = await client.updateLiffApp(
242 + 'liff-12345',
243 + {
244 + type: 'tall',
245 + url: 'https://example.com/myservice',
246 + },
247 + { accessToken: CUSTOM_ACCESS_TOKEN }
248 + );
249 +
250 + expect(res).toEqual(reply);
251 + });
252 + });
253 +
254 + describe('#deleteLiffApp', () => {
255 + it('should call api', async () => {
256 + expect.assertions(4);
257 +
258 + const { client, mock, headers } = createMock();
259 +
260 + const reply = {};
261 +
262 + mock.onDelete().reply(config => {
263 + expect(config.url).toEqual(
264 + 'https://api.line.me/liff/v1/apps/liff-12345'
265 + );
266 + expect(config.data).toEqual(undefined);
267 + expect(config.headers).toEqual(headers);
268 + return [200, reply];
269 + });
270 +
271 + const res = await client.deleteLiffApp('liff-12345');
272 +
273 + expect(res).toEqual(reply);
274 + });
275 +
276 + it('should work with custom access token', async () => {
277 + expect.assertions(4);
278 +
279 + const { client, mock, headers } = createMock({
280 + customAccessToken: CUSTOM_ACCESS_TOKEN,
281 + });
282 +
283 + const reply = {};
284 +
285 + mock.onDelete().reply(config => {
286 + expect(config.url).toEqual(
287 + 'https://api.line.me/liff/v1/apps/liff-12345'
288 + );
289 + expect(config.data).toEqual(undefined);
290 + expect(config.headers).toEqual(headers);
291 + return [200, reply];
292 + });
293 +
294 + const res = await client.deleteLiffApp('liff-12345', {
295 + accessToken: CUSTOM_ACCESS_TOKEN,
296 + });
297 +
298 + expect(res).toEqual(reply);
299 + });
300 + });
301 +});
1 +import fs from 'fs';
2 +import path from 'path';
3 +
4 +import MockAdapter from 'axios-mock-adapter';
5 +
6 +import LineClient from '../LineClient';
7 +
8 +const CUSTOM_ACCESS_TOKEN = '555555555';
9 +const ACCESS_TOKEN = '1234567890';
10 +const CHANNEL_SECRET = 'so-secret';
11 +
12 +const createMock = ({ customAccessToken } = {}) => {
13 + const client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
14 + const mock = new MockAdapter(client.axios);
15 + const headers = {
16 + Accept: 'application/json, text/plain, */*',
17 + 'Content-Type': 'application/json',
18 + Authorization: `Bearer ${customAccessToken || ACCESS_TOKEN}`,
19 + };
20 + return { client, mock, headers };
21 +};
22 +
23 +describe('Rich Menu', () => {
24 + describe('#getRichMenuList', () => {
25 + it('should call api', async () => {
26 + expect.assertions(4);
27 +
28 + const { client, mock, headers } = createMock();
29 +
30 + const reply = {
31 + richmenus: [
32 + {
33 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
34 + size: {
35 + width: 2500,
36 + height: 1686,
37 + },
38 + selected: false,
39 + name: 'Nice richmenu',
40 + chatBarText: 'Tap here',
41 + areas: [
42 + {
43 + bounds: {
44 + x: 0,
45 + y: 0,
46 + width: 2500,
47 + height: 1686,
48 + },
49 + action: {
50 + type: 'postback',
51 + data: 'action=buy&itemid=123',
52 + },
53 + },
54 + ],
55 + },
56 + ],
57 + };
58 +
59 + mock.onGet().reply(config => {
60 + expect(config.url).toEqual('https://api.line.me/v2/bot/richmenu/list');
61 + expect(config.data).toEqual(undefined);
62 + expect(config.headers).toEqual(headers);
63 + return [200, reply];
64 + });
65 +
66 + const res = await client.getRichMenuList();
67 +
68 + expect(res).toEqual([
69 + {
70 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
71 + size: {
72 + width: 2500,
73 + height: 1686,
74 + },
75 + selected: false,
76 + name: 'Nice richmenu',
77 + chatBarText: 'Tap here',
78 + areas: [
79 + {
80 + bounds: {
81 + x: 0,
82 + y: 0,
83 + width: 2500,
84 + height: 1686,
85 + },
86 + action: {
87 + type: 'postback',
88 + data: 'action=buy&itemid=123',
89 + },
90 + },
91 + ],
92 + },
93 + ]);
94 + });
95 +
96 + it('should work with custom access token', async () => {
97 + expect.assertions(4);
98 +
99 + const { client, mock, headers } = createMock({
100 + customAccessToken: CUSTOM_ACCESS_TOKEN,
101 + });
102 +
103 + const reply = {
104 + richmenus: [
105 + {
106 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
107 + size: {
108 + width: 2500,
109 + height: 1686,
110 + },
111 + selected: false,
112 + name: 'Nice richmenu',
113 + chatBarText: 'Tap here',
114 + areas: [
115 + {
116 + bounds: {
117 + x: 0,
118 + y: 0,
119 + width: 2500,
120 + height: 1686,
121 + },
122 + action: {
123 + type: 'postback',
124 + data: 'action=buy&itemid=123',
125 + },
126 + },
127 + ],
128 + },
129 + ],
130 + };
131 +
132 + mock.onGet().reply(config => {
133 + expect(config.url).toEqual('https://api.line.me/v2/bot/richmenu/list');
134 + expect(config.data).toEqual(undefined);
135 + expect(config.headers).toEqual(headers);
136 + return [200, reply];
137 + });
138 +
139 + const res = await client.getRichMenuList({
140 + accessToken: CUSTOM_ACCESS_TOKEN,
141 + });
142 +
143 + expect(res).toEqual([
144 + {
145 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
146 + size: {
147 + width: 2500,
148 + height: 1686,
149 + },
150 + selected: false,
151 + name: 'Nice richmenu',
152 + chatBarText: 'Tap here',
153 + areas: [
154 + {
155 + bounds: {
156 + x: 0,
157 + y: 0,
158 + width: 2500,
159 + height: 1686,
160 + },
161 + action: {
162 + type: 'postback',
163 + data: 'action=buy&itemid=123',
164 + },
165 + },
166 + ],
167 + },
168 + ]);
169 + });
170 + });
171 +
172 + describe('#getRichMenu', () => {
173 + it('should call api', async () => {
174 + expect.assertions(4);
175 +
176 + const { client, mock, headers } = createMock();
177 +
178 + const reply = {
179 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
180 + size: {
181 + width: 2500,
182 + height: 1686,
183 + },
184 + selected: false,
185 + name: 'Nice richmenu',
186 + chatBarText: 'Tap here',
187 + areas: [
188 + {
189 + bounds: {
190 + x: 0,
191 + y: 0,
192 + width: 2500,
193 + height: 1686,
194 + },
195 + action: {
196 + type: 'postback',
197 + data: 'action=buy&itemid=123',
198 + },
199 + },
200 + ],
201 + };
202 +
203 + mock.onGet().reply(config => {
204 + expect(config.url).toEqual(
205 + 'https://api.line.me/v2/bot/richmenu/richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
206 + );
207 + expect(config.data).toEqual(undefined);
208 + expect(config.headers).toEqual(headers);
209 + return [200, reply];
210 + });
211 +
212 + const res = await client.getRichMenu(
213 + 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
214 + );
215 +
216 + expect(res).toEqual(reply);
217 + });
218 +
219 + it('should work with custom access token', async () => {
220 + expect.assertions(4);
221 +
222 + const { client, mock, headers } = createMock({
223 + customAccessToken: CUSTOM_ACCESS_TOKEN,
224 + });
225 +
226 + const reply = {
227 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
228 + size: {
229 + width: 2500,
230 + height: 1686,
231 + },
232 + selected: false,
233 + name: 'Nice richmenu',
234 + chatBarText: 'Tap here',
235 + areas: [
236 + {
237 + bounds: {
238 + x: 0,
239 + y: 0,
240 + width: 2500,
241 + height: 1686,
242 + },
243 + action: {
244 + type: 'postback',
245 + data: 'action=buy&itemid=123',
246 + },
247 + },
248 + ],
249 + };
250 +
251 + mock.onGet().reply(config => {
252 + expect(config.url).toEqual(
253 + 'https://api.line.me/v2/bot/richmenu/richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
254 + );
255 + expect(config.data).toEqual(undefined);
256 + expect(config.headers).toEqual(headers);
257 + return [200, reply];
258 + });
259 +
260 + const res = await client.getRichMenu(
261 + 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
262 + { accessToken: CUSTOM_ACCESS_TOKEN }
263 + );
264 +
265 + expect(res).toEqual(reply);
266 + });
267 +
268 + it('should return null when no rich menu found', async () => {
269 + const { client, mock } = createMock();
270 +
271 + mock.onGet().reply(404, {
272 + message: 'richmenu not found',
273 + details: [],
274 + });
275 +
276 + const res = await client.getRichMenu(
277 + 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
278 + );
279 +
280 + expect(res).toEqual(null);
281 + });
282 + });
283 +
284 + describe('#createRichMenu', () => {
285 + it('should call api', async () => {
286 + expect.assertions(4);
287 +
288 + const { client, mock, headers } = createMock();
289 +
290 + const reply = {
291 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
292 + };
293 +
294 + const richMenuObject = {
295 + size: {
296 + width: 2500,
297 + height: 1686,
298 + },
299 + selected: false,
300 + name: 'Nice richmenu',
301 + chatBarText: 'Tap here',
302 + areas: [
303 + {
304 + bounds: {
305 + x: 0,
306 + y: 0,
307 + width: 2500,
308 + height: 1686,
309 + },
310 + action: {
311 + type: 'postback',
312 + data: 'action=buy&itemid=123',
313 + },
314 + },
315 + ],
316 + };
317 +
318 + mock.onPost().reply(config => {
319 + expect(config.url).toEqual('https://api.line.me/v2/bot/richmenu');
320 + expect(JSON.parse(config.data)).toEqual(richMenuObject);
321 + expect(config.headers).toEqual(headers);
322 + return [200, reply];
323 + });
324 +
325 + const res = await client.createRichMenu(richMenuObject);
326 +
327 + expect(res).toEqual(reply);
328 + });
329 +
330 + it('should work with custom access token', async () => {
331 + expect.assertions(4);
332 +
333 + const { client, mock, headers } = createMock({
334 + customAccessToken: CUSTOM_ACCESS_TOKEN,
335 + });
336 +
337 + const reply = {
338 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
339 + };
340 +
341 + const richMenuObject = {
342 + size: {
343 + width: 2500,
344 + height: 1686,
345 + },
346 + selected: false,
347 + name: 'Nice richmenu',
348 + chatBarText: 'Tap here',
349 + areas: [
350 + {
351 + bounds: {
352 + x: 0,
353 + y: 0,
354 + width: 2500,
355 + height: 1686,
356 + },
357 + action: {
358 + type: 'postback',
359 + data: 'action=buy&itemid=123',
360 + },
361 + },
362 + ],
363 + };
364 +
365 + mock.onPost().reply(config => {
366 + expect(config.url).toEqual('https://api.line.me/v2/bot/richmenu');
367 + expect(JSON.parse(config.data)).toEqual(richMenuObject);
368 + expect(config.headers).toEqual(headers);
369 + return [200, reply];
370 + });
371 +
372 + const res = await client.createRichMenu(richMenuObject, {
373 + accessToken: CUSTOM_ACCESS_TOKEN,
374 + });
375 +
376 + expect(res).toEqual(reply);
377 + });
378 + });
379 +
380 + describe('#deleteRichMenu', () => {
381 + it('should call api', async () => {
382 + expect.assertions(4);
383 +
384 + const { client, mock, headers } = createMock();
385 +
386 + const reply = {};
387 +
388 + mock.onDelete().reply(config => {
389 + expect(config.url).toEqual('https://api.line.me/v2/bot/richmenu/1');
390 + expect(config.data).toEqual(undefined);
391 + expect(config.headers).toEqual(headers);
392 + return [200, reply];
393 + });
394 +
395 + const res = await client.deleteRichMenu('1');
396 +
397 + expect(res).toEqual(reply);
398 + });
399 +
400 + it('should work with custom access token', async () => {
401 + expect.assertions(4);
402 +
403 + const { client, mock, headers } = createMock({
404 + customAccessToken: CUSTOM_ACCESS_TOKEN,
405 + });
406 +
407 + const reply = {};
408 +
409 + mock.onDelete().reply(config => {
410 + expect(config.url).toEqual('https://api.line.me/v2/bot/richmenu/1');
411 + expect(config.data).toEqual(undefined);
412 + expect(config.headers).toEqual(headers);
413 + return [200, reply];
414 + });
415 +
416 + const res = await client.deleteRichMenu('1', {
417 + accessToken: CUSTOM_ACCESS_TOKEN,
418 + });
419 +
420 + expect(res).toEqual(reply);
421 + });
422 + });
423 +
424 + describe('#getLinkedRichMenu', () => {
425 + it('should call api', async () => {
426 + expect.assertions(4);
427 +
428 + const { client, mock, headers } = createMock();
429 +
430 + const reply = {
431 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
432 + };
433 +
434 + mock.onGet().reply(config => {
435 + expect(config.url).toEqual(
436 + 'https://api.line.me/v2/bot/user/1/richmenu'
437 + );
438 + expect(config.data).toEqual(undefined);
439 + expect(config.headers).toEqual(headers);
440 + return [200, reply];
441 + });
442 +
443 + const res = await client.getLinkedRichMenu('1');
444 +
445 + expect(res).toEqual(reply);
446 + });
447 +
448 + it('should work with custom access token', async () => {
449 + expect.assertions(4);
450 +
451 + const { client, mock, headers } = createMock({
452 + customAccessToken: CUSTOM_ACCESS_TOKEN,
453 + });
454 +
455 + const reply = {
456 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
457 + };
458 +
459 + mock.onGet().reply(config => {
460 + expect(config.url).toEqual(
461 + 'https://api.line.me/v2/bot/user/1/richmenu'
462 + );
463 + expect(config.data).toEqual(undefined);
464 + expect(config.headers).toEqual(headers);
465 + return [200, reply];
466 + });
467 +
468 + const res = await client.getLinkedRichMenu('1', {
469 + accessToken: CUSTOM_ACCESS_TOKEN,
470 + });
471 +
472 + expect(res).toEqual(reply);
473 + });
474 +
475 + it('should return null when no rich menu found', async () => {
476 + const { client, mock } = createMock();
477 +
478 + mock.onGet().reply(404, {
479 + message: 'the user has no richmenu',
480 + details: [],
481 + });
482 +
483 + const res = await client.getLinkedRichMenu(
484 + 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
485 + );
486 +
487 + expect(res).toEqual(null);
488 + });
489 + });
490 +
491 + describe('#linkRichMenu', () => {
492 + it('should call api', async () => {
493 + expect.assertions(4);
494 +
495 + const { client, mock, headers } = createMock();
496 +
497 + const reply = {};
498 +
499 + mock.onPost().reply(config => {
500 + expect(config.url).toEqual(
501 + 'https://api.line.me/v2/bot/user/1/richmenu/2'
502 + );
503 + expect(config.data).toEqual(null);
504 + expect(config.headers).toEqual(headers);
505 + return [200, reply];
506 + });
507 +
508 + const res = await client.linkRichMenu('1', '2');
509 +
510 + expect(res).toEqual(reply);
511 + });
512 +
513 + it('should work with custom access token', async () => {
514 + expect.assertions(4);
515 +
516 + const { client, mock, headers } = createMock({
517 + customAccessToken: CUSTOM_ACCESS_TOKEN,
518 + });
519 +
520 + const reply = {};
521 +
522 + mock.onPost().reply(config => {
523 + expect(config.url).toEqual(
524 + 'https://api.line.me/v2/bot/user/1/richmenu/2'
525 + );
526 + expect(config.data).toEqual(null);
527 + expect(config.headers).toEqual(headers);
528 + return [200, reply];
529 + });
530 +
531 + const res = await client.linkRichMenu('1', '2', {
532 + accessToken: CUSTOM_ACCESS_TOKEN,
533 + });
534 +
535 + expect(res).toEqual(reply);
536 + });
537 + });
538 +
539 + describe('#unlinkRichMenu', () => {
540 + it('should call api', async () => {
541 + expect.assertions(4);
542 +
543 + const { client, mock, headers } = createMock();
544 +
545 + const reply = {};
546 +
547 + mock.onDelete().reply(config => {
548 + expect(config.url).toEqual(
549 + 'https://api.line.me/v2/bot/user/1/richmenu'
550 + );
551 + expect(config.data).toEqual(undefined);
552 + expect(config.headers).toEqual(headers);
553 + return [200, reply];
554 + });
555 +
556 + const res = await client.unlinkRichMenu('1');
557 +
558 + expect(res).toEqual(reply);
559 + });
560 +
561 + it('should work with custom access token', async () => {
562 + expect.assertions(4);
563 +
564 + const { client, mock, headers } = createMock({
565 + customAccessToken: CUSTOM_ACCESS_TOKEN,
566 + });
567 +
568 + const reply = {};
569 +
570 + mock.onDelete().reply(config => {
571 + expect(config.url).toEqual(
572 + 'https://api.line.me/v2/bot/user/1/richmenu'
573 + );
574 + expect(config.data).toEqual(undefined);
575 + expect(config.headers).toEqual(headers);
576 + return [200, reply];
577 + });
578 +
579 + const res = await client.unlinkRichMenu('1', {
580 + accessToken: CUSTOM_ACCESS_TOKEN,
581 + });
582 +
583 + expect(res).toEqual(reply);
584 + });
585 + });
586 +
587 + describe('#uploadRichMenuImage', () => {
588 + it('should call api', async () => {
589 + expect.assertions(4);
590 +
591 + const { client, mock, headers } = createMock();
592 +
593 + const reply = {};
594 +
595 + const buffer = await new Promise((resolve, reject) => {
596 + fs.readFile(path.join(__dirname, 'fixture.png'), (err, buf) => {
597 + if (err) {
598 + reject(err);
599 + } else {
600 + resolve(buf);
601 + }
602 + });
603 + });
604 +
605 + mock.onPost().reply(config => {
606 + expect(config.url).toEqual(
607 + 'https://api.line.me/v2/bot/richmenu/1/content'
608 + );
609 + expect(config.data).toEqual(buffer);
610 + expect(config.headers).toEqual({
611 + ...headers,
612 + 'Content-Type': 'image/png',
613 + });
614 + return [200, reply];
615 + });
616 +
617 + const res = await client.uploadRichMenuImage('1', buffer);
618 +
619 + expect(res).toEqual(reply);
620 + });
621 +
622 + it('should work with custom access token', async () => {
623 + expect.assertions(4);
624 +
625 + const { client, mock, headers } = createMock({
626 + customAccessToken: CUSTOM_ACCESS_TOKEN,
627 + });
628 +
629 + const reply = {};
630 +
631 + const buffer = await new Promise((resolve, reject) => {
632 + fs.readFile(path.join(__dirname, 'fixture.png'), (err, buf) => {
633 + if (err) {
634 + reject(err);
635 + } else {
636 + resolve(buf);
637 + }
638 + });
639 + });
640 +
641 + mock.onPost().reply(config => {
642 + expect(config.url).toEqual(
643 + 'https://api.line.me/v2/bot/richmenu/1/content'
644 + );
645 + expect(config.data).toEqual(buffer);
646 + expect(config.headers).toEqual({
647 + ...headers,
648 + 'Content-Type': 'image/png',
649 + });
650 + return [200, reply];
651 + });
652 +
653 + const res = await client.uploadRichMenuImage('1', buffer, {
654 + accessToken: CUSTOM_ACCESS_TOKEN,
655 + });
656 +
657 + expect(res).toEqual(reply);
658 + });
659 +
660 + it('should throw error when ', async () => {
661 + expect.assertions(1);
662 +
663 + const { client, mock, headers } = createMock();
664 +
665 + const reply = {};
666 +
667 + mock.onPost().reply(config => {
668 + expect(config.url).toEqual(
669 + 'https://api.line.me/v2/bot/richmenu/1/content'
670 + );
671 + expect(config.data).toEqual(undefined);
672 + expect(config.headers).toEqual(headers);
673 + return [200, reply];
674 + });
675 +
676 + let error;
677 + try {
678 + await client.uploadRichMenuImage('1', Buffer.from('a content buffer'));
679 + } catch (err) {
680 + error = err;
681 + }
682 +
683 + expect(error.message).toMatch(/image\/(jpeg|png)/);
684 + });
685 + });
686 +
687 + describe('#downloadRichMenuImage', () => {
688 + it('should call api', async () => {
689 + expect.assertions(4);
690 +
691 + const { client, mock, headers } = createMock();
692 +
693 + const reply = Buffer.from('a content buffer');
694 +
695 + mock.onGet().reply(config => {
696 + expect(config.url).toEqual(
697 + 'https://api.line.me/v2/bot/richmenu/1/content'
698 + );
699 + expect(config.data).toEqual(undefined);
700 + expect(config.headers).toEqual(headers);
701 + return [200, reply];
702 + });
703 +
704 + const res = await client.downloadRichMenuImage('1');
705 +
706 + expect(res).toEqual(reply);
707 + });
708 +
709 + it('should work with custom access token', async () => {
710 + expect.assertions(4);
711 + const { client, mock, headers } = createMock({
712 + customAccessToken: CUSTOM_ACCESS_TOKEN,
713 + });
714 +
715 + const reply = Buffer.from('a content buffer');
716 +
717 + mock.onGet().reply(config => {
718 + expect(config.url).toEqual(
719 + 'https://api.line.me/v2/bot/richmenu/1/content'
720 + );
721 + expect(config.data).toEqual(undefined);
722 + expect(config.headers).toEqual(headers);
723 + return [200, reply];
724 + });
725 +
726 + const res = await client.downloadRichMenuImage('1', {
727 + accessToken: CUSTOM_ACCESS_TOKEN,
728 + });
729 +
730 + expect(res).toEqual(reply);
731 + });
732 +
733 + it('should return null when no rich menu image found', async () => {
734 + const { client, mock } = createMock();
735 +
736 + mock.onGet().reply(404, Buffer.from('{"message":"Not found"}'));
737 +
738 + const res = await client.downloadRichMenuImage(
739 + 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
740 + );
741 +
742 + expect(res).toEqual(null);
743 + });
744 + });
745 +
746 + describe('#getDefaultRichMenu', () => {
747 + it('should call api', async () => {
748 + expect.assertions(4);
749 +
750 + const { client, mock, headers } = createMock();
751 +
752 + const reply = {
753 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
754 + };
755 +
756 + mock.onGet().reply(config => {
757 + expect(config.url).toEqual(
758 + 'https://api.line.me/v2/bot/user/all/richmenu'
759 + );
760 + expect(config.data).toEqual(undefined);
761 + expect(config.headers).toEqual(headers);
762 + return [200, reply];
763 + });
764 +
765 + const res = await client.getDefaultRichMenu();
766 +
767 + expect(res).toEqual(reply);
768 + });
769 +
770 + it('should work with custom access token', async () => {
771 + expect.assertions(4);
772 +
773 + const { client, mock, headers } = createMock({
774 + customAccessToken: CUSTOM_ACCESS_TOKEN,
775 + });
776 +
777 + const reply = {
778 + richMenuId: 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
779 + };
780 +
781 + mock.onGet().reply(config => {
782 + expect(config.url).toEqual(
783 + 'https://api.line.me/v2/bot/user/all/richmenu'
784 + );
785 + expect(config.data).toEqual(undefined);
786 + expect(config.headers).toEqual(headers);
787 + return [200, reply];
788 + });
789 +
790 + const res = await client.getDefaultRichMenu({
791 + accessToken: CUSTOM_ACCESS_TOKEN,
792 + });
793 +
794 + expect(res).toEqual(reply);
795 + });
796 +
797 + it('should return null when no default rich menu found', async () => {
798 + const { client, mock } = createMock();
799 +
800 + mock.onGet().reply(404, {
801 + message: 'no default richmenu',
802 + details: [],
803 + });
804 +
805 + const res = await client.getDefaultRichMenu(
806 + 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
807 + );
808 +
809 + expect(res).toEqual(null);
810 + });
811 + });
812 +
813 + describe('#setDefaultRichMenu', () => {
814 + it('should call api', async () => {
815 + expect.assertions(4);
816 +
817 + const { client, mock, headers } = createMock();
818 +
819 + const reply = {};
820 +
821 + mock.onPost().reply(config => {
822 + expect(config.url).toEqual(
823 + 'https://api.line.me/v2/bot/user/all/richmenu/richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
824 + );
825 + expect(config.data).toEqual(null);
826 + expect(config.headers).toEqual(headers);
827 + return [200, reply];
828 + });
829 +
830 + const res = await client.setDefaultRichMenu(
831 + 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
832 + );
833 +
834 + expect(res).toEqual(reply);
835 + });
836 +
837 + it('should work with custom access token', async () => {
838 + expect.assertions(4);
839 +
840 + const { client, mock, headers } = createMock({
841 + customAccessToken: CUSTOM_ACCESS_TOKEN,
842 + });
843 +
844 + const reply = {};
845 +
846 + mock.onPost().reply(config => {
847 + expect(config.url).toEqual(
848 + 'https://api.line.me/v2/bot/user/all/richmenu/richmenu-8dfdfc571eca39c0ffcd1f799519c5b5'
849 + );
850 + expect(config.data).toEqual(null);
851 + expect(config.headers).toEqual(headers);
852 + return [200, reply];
853 + });
854 +
855 + const res = await client.setDefaultRichMenu(
856 + 'richmenu-8dfdfc571eca39c0ffcd1f799519c5b5',
857 + { accessToken: CUSTOM_ACCESS_TOKEN }
858 + );
859 +
860 + expect(res).toEqual(reply);
861 + });
862 + });
863 +
864 + describe('#deleteDefaultRichMenu', () => {
865 + it('should call api', async () => {
866 + expect.assertions(4);
867 +
868 + const { client, mock, headers } = createMock();
869 +
870 + const reply = {};
871 +
872 + mock.onDelete().reply(config => {
873 + expect(config.url).toEqual(
874 + 'https://api.line.me/v2/bot/user/all/richmenu'
875 + );
876 + expect(config.data).toEqual(undefined);
877 + expect(config.headers).toEqual(headers);
878 + return [200, reply];
879 + });
880 +
881 + const res = await client.deleteDefaultRichMenu();
882 +
883 + expect(res).toEqual(reply);
884 + });
885 + });
886 +
887 + it('should work with custom access token', async () => {
888 + expect.assertions(4);
889 +
890 + const { client, mock, headers } = createMock({
891 + customAccessToken: CUSTOM_ACCESS_TOKEN,
892 + });
893 +
894 + const reply = {};
895 +
896 + mock.onDelete().reply(config => {
897 + expect(config.url).toEqual(
898 + 'https://api.line.me/v2/bot/user/all/richmenu'
899 + );
900 + expect(config.data).toEqual(undefined);
901 + expect(config.headers).toEqual(headers);
902 + return [200, reply];
903 + });
904 +
905 + const res = await client.deleteDefaultRichMenu({
906 + accessToken: CUSTOM_ACCESS_TOKEN,
907 + });
908 +
909 + expect(res).toEqual(reply);
910 + });
911 +});
1 +import MockAdapter from 'axios-mock-adapter';
2 +
3 +import LineClient from '../LineClient';
4 +
5 +const RECIPIENT_ID = '1QAZ2WSX';
6 +const CUSTOM_ACCESS_TOKEN = '555555555';
7 +const ACCESS_TOKEN = '1234567890';
8 +const CHANNEL_SECRET = 'so-secret';
9 +
10 +const createMock = ({ customAccessToken } = {}) => {
11 + const client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
12 + const mock = new MockAdapter(client.axios);
13 + const headers = {
14 + Accept: 'application/json, text/plain, */*',
15 + 'Content-Type': 'application/json',
16 + Authorization: `Bearer ${customAccessToken || ACCESS_TOKEN}`,
17 + };
18 + return { client, mock, headers };
19 +};
20 +
21 +describe('Multicast', () => {
22 + describe('#multicastRawBody', () => {
23 + it('should call multicast api', async () => {
24 + expect.assertions(4);
25 +
26 + const { client, mock, headers } = createMock();
27 +
28 + const reply = {};
29 +
30 + mock.onPost().reply(config => {
31 + expect(config.url).toEqual(
32 + 'https://api.line.me/v2/bot/message/multicast'
33 + );
34 + expect(JSON.parse(config.data)).toEqual({
35 + to: [RECIPIENT_ID],
36 + messages: [{ type: 'text', text: 'Hello!' }],
37 + });
38 + expect(config.headers).toEqual(headers);
39 + return [200, reply];
40 + });
41 +
42 + const res = await client.multicastRawBody({
43 + to: [RECIPIENT_ID],
44 + messages: [
45 + {
46 + type: 'text',
47 + text: 'Hello!',
48 + },
49 + ],
50 + });
51 +
52 + expect(res).toEqual(reply);
53 + });
54 +
55 + it('should work with custom access token', async () => {
56 + expect.assertions(4);
57 +
58 + const { client, mock, headers } = createMock({
59 + customAccessToken: CUSTOM_ACCESS_TOKEN,
60 + });
61 +
62 + const reply = {};
63 +
64 + mock.onPost().reply(config => {
65 + expect(config.url).toEqual(
66 + 'https://api.line.me/v2/bot/message/multicast'
67 + );
68 + expect(JSON.parse(config.data)).toEqual({
69 + to: [RECIPIENT_ID],
70 + messages: [{ type: 'text', text: 'Hello!' }],
71 + });
72 + expect(config.headers).toEqual(headers);
73 + return [200, reply];
74 + });
75 +
76 + const res = await client.multicastRawBody(
77 + {
78 + to: [RECIPIENT_ID],
79 + messages: [
80 + {
81 + type: 'text',
82 + text: 'Hello!',
83 + },
84 + ],
85 + },
86 + { accessToken: CUSTOM_ACCESS_TOKEN }
87 + );
88 +
89 + expect(res).toEqual(reply);
90 + });
91 + });
92 +
93 + describe('#multicast', () => {
94 + it('should call multicast api', async () => {
95 + expect.assertions(4);
96 +
97 + const { client, mock, headers } = createMock();
98 +
99 + const reply = {};
100 +
101 + mock.onPost().reply(config => {
102 + expect(config.url).toEqual(
103 + 'https://api.line.me/v2/bot/message/multicast'
104 + );
105 + expect(JSON.parse(config.data)).toEqual({
106 + to: [RECIPIENT_ID],
107 + messages: [{ type: 'text', text: 'Hello!' }],
108 + });
109 + expect(config.headers).toEqual(headers);
110 + return [200, reply];
111 + });
112 +
113 + const res = await client.multicast(
114 + [RECIPIENT_ID],
115 + [
116 + {
117 + type: 'text',
118 + text: 'Hello!',
119 + },
120 + ]
121 + );
122 +
123 + expect(res).toEqual(reply);
124 + });
125 +
126 + it('should work with custom access token', async () => {
127 + expect.assertions(4);
128 +
129 + const { client, mock, headers } = createMock({
130 + customAccessToken: CUSTOM_ACCESS_TOKEN,
131 + });
132 +
133 + const reply = {};
134 +
135 + mock.onPost().reply(config => {
136 + expect(config.url).toEqual(
137 + 'https://api.line.me/v2/bot/message/multicast'
138 + );
139 + expect(JSON.parse(config.data)).toEqual({
140 + to: [RECIPIENT_ID],
141 + messages: [{ type: 'text', text: 'Hello!' }],
142 + });
143 + expect(config.headers).toEqual(headers);
144 + return [200, reply];
145 + });
146 +
147 + const res = await client.multicast(
148 + [RECIPIENT_ID],
149 + [
150 + {
151 + type: 'text',
152 + text: 'Hello!',
153 + },
154 + ],
155 + { accessToken: CUSTOM_ACCESS_TOKEN }
156 + );
157 +
158 + expect(res).toEqual(reply);
159 + });
160 + });
161 +
162 + describe('#multicastText', () => {
163 + it('should call multicast api', async () => {
164 + expect.assertions(4);
165 +
166 + const { client, mock, headers } = createMock();
167 +
168 + const reply = {};
169 +
170 + mock.onPost().reply(config => {
171 + expect(config.url).toEqual(
172 + 'https://api.line.me/v2/bot/message/multicast'
173 + );
174 + expect(JSON.parse(config.data)).toEqual({
175 + to: [RECIPIENT_ID],
176 + messages: [{ type: 'text', text: 'Hello!' }],
177 + });
178 + expect(config.headers).toEqual(headers);
179 + return [200, reply];
180 + });
181 +
182 + const res = await client.multicastText([RECIPIENT_ID], 'Hello!');
183 +
184 + expect(res).toEqual(reply);
185 + });
186 +
187 + it('should work with custom access token', async () => {
188 + expect.assertions(4);
189 +
190 + const { client, mock, headers } = createMock({
191 + customAccessToken: CUSTOM_ACCESS_TOKEN,
192 + });
193 +
194 + const reply = {};
195 +
196 + mock.onPost().reply(config => {
197 + expect(config.url).toEqual(
198 + 'https://api.line.me/v2/bot/message/multicast'
199 + );
200 + expect(JSON.parse(config.data)).toEqual({
201 + to: [RECIPIENT_ID],
202 + messages: [{ type: 'text', text: 'Hello!' }],
203 + });
204 + expect(config.headers).toEqual(headers);
205 + return [200, reply];
206 + });
207 +
208 + const res = await client.multicastText([RECIPIENT_ID], 'Hello!', {
209 + accessToken: CUSTOM_ACCESS_TOKEN,
210 + });
211 +
212 + expect(res).toEqual(reply);
213 + });
214 + });
215 +
216 + describe('#multicastImage', () => {
217 + it('should call multicast api', async () => {
218 + expect.assertions(4);
219 +
220 + const { client, mock, headers } = createMock();
221 +
222 + const reply = {};
223 +
224 + mock.onPost().reply(config => {
225 + expect(config.url).toEqual(
226 + 'https://api.line.me/v2/bot/message/multicast'
227 + );
228 + expect(JSON.parse(config.data)).toEqual({
229 + to: [RECIPIENT_ID],
230 + messages: [
231 + {
232 + type: 'image',
233 + originalContentUrl: 'https://example.com/original.jpg',
234 + previewImageUrl: 'https://example.com/preview.jpg',
235 + },
236 + ],
237 + });
238 + expect(config.headers).toEqual(headers);
239 + return [200, reply];
240 + });
241 +
242 + const res = await client.multicastImage(
243 + [RECIPIENT_ID],
244 + 'https://example.com/original.jpg',
245 + 'https://example.com/preview.jpg'
246 + );
247 +
248 + expect(res).toEqual(reply);
249 + });
250 +
251 + it('should use contentUrl as fallback', async () => {
252 + expect.assertions(4);
253 +
254 + const { client, mock, headers } = createMock();
255 +
256 + const reply = {};
257 +
258 + mock.onPost().reply(config => {
259 + expect(config.url).toEqual(
260 + 'https://api.line.me/v2/bot/message/multicast'
261 + );
262 + expect(JSON.parse(config.data)).toEqual({
263 + to: [RECIPIENT_ID],
264 + messages: [
265 + {
266 + type: 'image',
267 + originalContentUrl: 'https://example.com/original.jpg',
268 + previewImageUrl: 'https://example.com/original.jpg',
269 + },
270 + ],
271 + });
272 + expect(config.headers).toEqual(headers);
273 + return [200, reply];
274 + });
275 +
276 + const res = await client.multicastImage(
277 + [RECIPIENT_ID],
278 + 'https://example.com/original.jpg'
279 + );
280 +
281 + expect(res).toEqual(reply);
282 + });
283 +
284 + it('should call multicast api with object image arg', async () => {
285 + expect.assertions(4);
286 +
287 + const { client, mock, headers } = createMock();
288 +
289 + const reply = {};
290 +
291 + mock.onPost().reply(config => {
292 + expect(config.url).toEqual(
293 + 'https://api.line.me/v2/bot/message/multicast'
294 + );
295 + expect(JSON.parse(config.data)).toEqual({
296 + to: [RECIPIENT_ID],
297 + messages: [
298 + {
299 + type: 'image',
300 + originalContentUrl: 'https://example.com/original.jpg',
301 + previewImageUrl: 'https://example.com/preview.jpg',
302 + },
303 + ],
304 + });
305 + expect(config.headers).toEqual(headers);
306 + return [200, reply];
307 + });
308 +
309 + const res = await client.multicastImage([RECIPIENT_ID], {
310 + originalContentUrl: 'https://example.com/original.jpg',
311 + previewImageUrl: 'https://example.com/preview.jpg',
312 + });
313 +
314 + expect(res).toEqual(reply);
315 + });
316 +
317 + it('should work with custom access token', async () => {
318 + expect.assertions(4);
319 +
320 + const { client, mock, headers } = createMock({
321 + customAccessToken: CUSTOM_ACCESS_TOKEN,
322 + });
323 +
324 + const reply = {};
325 +
326 + mock.onPost().reply(config => {
327 + expect(config.url).toEqual(
328 + 'https://api.line.me/v2/bot/message/multicast'
329 + );
330 + expect(JSON.parse(config.data)).toEqual({
331 + to: [RECIPIENT_ID],
332 + messages: [
333 + {
334 + type: 'image',
335 + originalContentUrl: 'https://example.com/original.jpg',
336 + previewImageUrl: 'https://example.com/preview.jpg',
337 + },
338 + ],
339 + });
340 + expect(config.headers).toEqual(headers);
341 + return [200, reply];
342 + });
343 +
344 + const res = await client.multicastImage(
345 + [RECIPIENT_ID],
346 + {
347 + originalContentUrl: 'https://example.com/original.jpg',
348 + previewImageUrl: 'https://example.com/preview.jpg',
349 + },
350 + { accessToken: CUSTOM_ACCESS_TOKEN }
351 + );
352 +
353 + expect(res).toEqual(reply);
354 + });
355 + });
356 +
357 + describe('#multicastVideo', () => {
358 + it('should call multicast api', async () => {
359 + expect.assertions(4);
360 +
361 + const { client, mock, headers } = createMock();
362 +
363 + const reply = {};
364 +
365 + mock.onPost().reply(config => {
366 + expect(config.url).toEqual(
367 + 'https://api.line.me/v2/bot/message/multicast'
368 + );
369 + expect(JSON.parse(config.data)).toEqual({
370 + to: [RECIPIENT_ID],
371 + messages: [
372 + {
373 + type: 'video',
374 + originalContentUrl: 'https://example.com/original.mp4',
375 + previewImageUrl: 'https://example.com/preview.jpg',
376 + },
377 + ],
378 + });
379 + expect(config.headers).toEqual(headers);
380 + return [200, reply];
381 + });
382 +
383 + const res = await client.multicastVideo(
384 + [RECIPIENT_ID],
385 + 'https://example.com/original.mp4',
386 + 'https://example.com/preview.jpg'
387 + );
388 +
389 + expect(res).toEqual(reply);
390 + });
391 +
392 + it('should call multicast api with object video arg', async () => {
393 + expect.assertions(4);
394 +
395 + const { client, mock, headers } = createMock();
396 +
397 + const reply = {};
398 +
399 + mock.onPost().reply(config => {
400 + expect(config.url).toEqual(
401 + 'https://api.line.me/v2/bot/message/multicast'
402 + );
403 + expect(JSON.parse(config.data)).toEqual({
404 + to: [RECIPIENT_ID],
405 + messages: [
406 + {
407 + type: 'video',
408 + originalContentUrl: 'https://example.com/original.mp4',
409 + previewImageUrl: 'https://example.com/preview.jpg',
410 + },
411 + ],
412 + });
413 + expect(config.headers).toEqual(headers);
414 + return [200, reply];
415 + });
416 +
417 + const res = await client.multicastVideo([RECIPIENT_ID], {
418 + originalContentUrl: 'https://example.com/original.mp4',
419 + previewImageUrl: 'https://example.com/preview.jpg',
420 + });
421 +
422 + expect(res).toEqual(reply);
423 + });
424 +
425 + it('should work with custom access token', async () => {
426 + expect.assertions(4);
427 +
428 + const { client, mock, headers } = createMock({
429 + customAccessToken: CUSTOM_ACCESS_TOKEN,
430 + });
431 +
432 + const reply = {};
433 +
434 + mock.onPost().reply(config => {
435 + expect(config.url).toEqual(
436 + 'https://api.line.me/v2/bot/message/multicast'
437 + );
438 + expect(JSON.parse(config.data)).toEqual({
439 + to: [RECIPIENT_ID],
440 + messages: [
441 + {
442 + type: 'video',
443 + originalContentUrl: 'https://example.com/original.mp4',
444 + previewImageUrl: 'https://example.com/preview.jpg',
445 + },
446 + ],
447 + });
448 + expect(config.headers).toEqual(headers);
449 + return [200, reply];
450 + });
451 +
452 + const res = await client.multicastVideo(
453 + [RECIPIENT_ID],
454 + {
455 + originalContentUrl: 'https://example.com/original.mp4',
456 + previewImageUrl: 'https://example.com/preview.jpg',
457 + },
458 + { accessToken: CUSTOM_ACCESS_TOKEN }
459 + );
460 +
461 + expect(res).toEqual(reply);
462 + });
463 + });
464 +
465 + describe('#multicastAudio', () => {
466 + it('should call multicast api', async () => {
467 + expect.assertions(4);
468 +
469 + const { client, mock, headers } = createMock();
470 +
471 + const reply = {};
472 +
473 + mock.onPost().reply(config => {
474 + expect(config.url).toEqual(
475 + 'https://api.line.me/v2/bot/message/multicast'
476 + );
477 + expect(JSON.parse(config.data)).toEqual({
478 + to: [RECIPIENT_ID],
479 + messages: [
480 + {
481 + type: 'audio',
482 + originalContentUrl: 'https://example.com/original.m4a',
483 + duration: 240000,
484 + },
485 + ],
486 + });
487 + expect(config.headers).toEqual(headers);
488 + return [200, reply];
489 + });
490 +
491 + const res = await client.multicastAudio(
492 + [RECIPIENT_ID],
493 + 'https://example.com/original.m4a',
494 + 240000
495 + );
496 +
497 + expect(res).toEqual(reply);
498 + });
499 +
500 + it('should call multicast api with object audio arg', async () => {
501 + expect.assertions(4);
502 +
503 + const { client, mock, headers } = createMock();
504 +
505 + const reply = {};
506 +
507 + mock.onPost().reply(config => {
508 + expect(config.url).toEqual(
509 + 'https://api.line.me/v2/bot/message/multicast'
510 + );
511 + expect(JSON.parse(config.data)).toEqual({
512 + to: [RECIPIENT_ID],
513 + messages: [
514 + {
515 + type: 'audio',
516 + originalContentUrl: 'https://example.com/original.m4a',
517 + duration: 240000,
518 + },
519 + ],
520 + });
521 + expect(config.headers).toEqual(headers);
522 + return [200, reply];
523 + });
524 +
525 + const res = await client.multicastAudio([RECIPIENT_ID], {
526 + originalContentUrl: 'https://example.com/original.m4a',
527 + duration: 240000,
528 + });
529 +
530 + expect(res).toEqual(reply);
531 + });
532 +
533 + it('should work with custom access token', async () => {
534 + expect.assertions(4);
535 +
536 + const { client, mock, headers } = createMock({
537 + customAccessToken: CUSTOM_ACCESS_TOKEN,
538 + });
539 +
540 + const reply = {};
541 +
542 + mock.onPost().reply(config => {
543 + expect(config.url).toEqual(
544 + 'https://api.line.me/v2/bot/message/multicast'
545 + );
546 + expect(JSON.parse(config.data)).toEqual({
547 + to: [RECIPIENT_ID],
548 + messages: [
549 + {
550 + type: 'audio',
551 + originalContentUrl: 'https://example.com/original.m4a',
552 + duration: 240000,
553 + },
554 + ],
555 + });
556 + expect(config.headers).toEqual(headers);
557 + return [200, reply];
558 + });
559 +
560 + const res = await client.multicastAudio(
561 + [RECIPIENT_ID],
562 + {
563 + originalContentUrl: 'https://example.com/original.m4a',
564 + duration: 240000,
565 + },
566 + {
567 + accessToken: CUSTOM_ACCESS_TOKEN,
568 + }
569 + );
570 +
571 + expect(res).toEqual(reply);
572 + });
573 + });
574 +
575 + describe('#multicastLocation', () => {
576 + it('should call multicast api', async () => {
577 + expect.assertions(4);
578 +
579 + const { client, mock, headers } = createMock();
580 +
581 + const reply = {};
582 +
583 + mock.onPost().reply(config => {
584 + expect(config.url).toEqual(
585 + 'https://api.line.me/v2/bot/message/multicast'
586 + );
587 + expect(JSON.parse(config.data)).toEqual({
588 + to: [RECIPIENT_ID],
589 + messages: [
590 + {
591 + type: 'location',
592 + title: 'my location',
593 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
594 + latitude: 35.65910807942215,
595 + longitude: 139.70372892916203,
596 + },
597 + ],
598 + });
599 + expect(config.headers).toEqual(headers);
600 + return [200, reply];
601 + });
602 +
603 + const res = await client.multicastLocation([RECIPIENT_ID], {
604 + title: 'my location',
605 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
606 + latitude: 35.65910807942215,
607 + longitude: 139.70372892916203,
608 + });
609 +
610 + expect(res).toEqual(reply);
611 + });
612 +
613 + it('should work with custom access token', async () => {
614 + expect.assertions(4);
615 +
616 + const { client, mock, headers } = createMock({
617 + customAccessToken: CUSTOM_ACCESS_TOKEN,
618 + });
619 +
620 + const reply = {};
621 +
622 + mock.onPost().reply(config => {
623 + expect(config.url).toEqual(
624 + 'https://api.line.me/v2/bot/message/multicast'
625 + );
626 + expect(JSON.parse(config.data)).toEqual({
627 + to: [RECIPIENT_ID],
628 + messages: [
629 + {
630 + type: 'location',
631 + title: 'my location',
632 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
633 + latitude: 35.65910807942215,
634 + longitude: 139.70372892916203,
635 + },
636 + ],
637 + });
638 + expect(config.headers).toEqual(headers);
639 + return [200, reply];
640 + });
641 +
642 + const res = await client.multicastLocation(
643 + [RECIPIENT_ID],
644 + {
645 + title: 'my location',
646 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
647 + latitude: 35.65910807942215,
648 + longitude: 139.70372892916203,
649 + },
650 + { accessToken: CUSTOM_ACCESS_TOKEN }
651 + );
652 +
653 + expect(res).toEqual(reply);
654 + });
655 + });
656 +
657 + describe('#multicastSticker', () => {
658 + it('should call multicast api', async () => {
659 + expect.assertions(4);
660 +
661 + const { client, mock, headers } = createMock();
662 +
663 + const reply = {};
664 +
665 + mock.onPost().reply(config => {
666 + expect(config.url).toEqual(
667 + 'https://api.line.me/v2/bot/message/multicast'
668 + );
669 + expect(JSON.parse(config.data)).toEqual({
670 + to: [RECIPIENT_ID],
671 + messages: [
672 + {
673 + type: 'sticker',
674 + packageId: '1',
675 + stickerId: '1',
676 + },
677 + ],
678 + });
679 + expect(config.headers).toEqual(headers);
680 + return [200, reply];
681 + });
682 +
683 + const res = await client.multicastSticker([RECIPIENT_ID], '1', '1');
684 +
685 + expect(res).toEqual(reply);
686 + });
687 +
688 + it('should call multicast api with object sticker arg', async () => {
689 + expect.assertions(4);
690 +
691 + const { client, mock, headers } = createMock();
692 +
693 + const reply = {};
694 +
695 + mock.onPost().reply(config => {
696 + expect(config.url).toEqual(
697 + 'https://api.line.me/v2/bot/message/multicast'
698 + );
699 + expect(JSON.parse(config.data)).toEqual({
700 + to: [RECIPIENT_ID],
701 + messages: [
702 + {
703 + type: 'sticker',
704 + packageId: '1',
705 + stickerId: '1',
706 + },
707 + ],
708 + });
709 + expect(config.headers).toEqual(headers);
710 + return [200, reply];
711 + });
712 +
713 + const res = await client.multicastSticker([RECIPIENT_ID], {
714 + packageId: '1',
715 + stickerId: '1',
716 + });
717 +
718 + expect(res).toEqual(reply);
719 + });
720 +
721 + it('should work with custom access token', async () => {
722 + expect.assertions(4);
723 +
724 + const { client, mock, headers } = createMock({
725 + customAccessToken: CUSTOM_ACCESS_TOKEN,
726 + });
727 +
728 + const reply = {};
729 +
730 + mock.onPost().reply(config => {
731 + expect(config.url).toEqual(
732 + 'https://api.line.me/v2/bot/message/multicast'
733 + );
734 + expect(JSON.parse(config.data)).toEqual({
735 + to: [RECIPIENT_ID],
736 + messages: [
737 + {
738 + type: 'sticker',
739 + packageId: '1',
740 + stickerId: '1',
741 + },
742 + ],
743 + });
744 + expect(config.headers).toEqual(headers);
745 + return [200, reply];
746 + });
747 +
748 + const res = await client.multicastSticker(
749 + [RECIPIENT_ID],
750 + {
751 + packageId: '1',
752 + stickerId: '1',
753 + },
754 + { accessToken: CUSTOM_ACCESS_TOKEN }
755 + );
756 +
757 + expect(res).toEqual(reply);
758 + });
759 + });
760 +
761 + describe('#multicastImagemap', () => {
762 + it('should call multicast api', async () => {
763 + expect.assertions(4);
764 +
765 + const { client, mock, headers } = createMock();
766 +
767 + const reply = {};
768 +
769 + mock.onPost().reply(config => {
770 + expect(config.url).toEqual(
771 + 'https://api.line.me/v2/bot/message/multicast'
772 + );
773 + expect(JSON.parse(config.data)).toEqual({
774 + to: [RECIPIENT_ID],
775 + messages: [
776 + {
777 + type: 'imagemap',
778 + baseUrl: 'https://example.com/bot/images/rm001',
779 + altText: 'this is an imagemap',
780 + baseSize: {
781 + height: 1040,
782 + width: 1040,
783 + },
784 + actions: [
785 + {
786 + type: 'uri',
787 + linkUri: 'https://example.com/',
788 + area: {
789 + x: 0,
790 + y: 0,
791 + width: 520,
792 + height: 1040,
793 + },
794 + },
795 + {
796 + type: 'message',
797 + text: 'hello',
798 + area: {
799 + x: 520,
800 + y: 0,
801 + width: 520,
802 + height: 1040,
803 + },
804 + },
805 + ],
806 + },
807 + ],
808 + });
809 + expect(config.headers).toEqual(headers);
810 + return [200, reply];
811 + });
812 +
813 + const res = await client.multicastImagemap(
814 + [RECIPIENT_ID],
815 + 'this is an imagemap',
816 + {
817 + baseUrl: 'https://example.com/bot/images/rm001',
818 + baseHeight: 1040,
819 + baseWidth: 1040,
820 + actions: [
821 + {
822 + type: 'uri',
823 + linkUri: 'https://example.com/',
824 + area: {
825 + x: 0,
826 + y: 0,
827 + width: 520,
828 + height: 1040,
829 + },
830 + },
831 + {
832 + type: 'message',
833 + text: 'hello',
834 + area: {
835 + x: 520,
836 + y: 0,
837 + width: 520,
838 + height: 1040,
839 + },
840 + },
841 + ],
842 + }
843 + );
844 +
845 + expect(res).toEqual(reply);
846 + });
847 +
848 + it('should work with custom access token', async () => {
849 + expect.assertions(4);
850 +
851 + const { client, mock, headers } = createMock({
852 + customAccessToken: CUSTOM_ACCESS_TOKEN,
853 + });
854 +
855 + const reply = {};
856 +
857 + mock.onPost().reply(config => {
858 + expect(config.url).toEqual(
859 + 'https://api.line.me/v2/bot/message/multicast'
860 + );
861 + expect(JSON.parse(config.data)).toEqual({
862 + to: [RECIPIENT_ID],
863 + messages: [
864 + {
865 + type: 'imagemap',
866 + baseUrl: 'https://example.com/bot/images/rm001',
867 + altText: 'this is an imagemap',
868 + baseSize: {
869 + height: 1040,
870 + width: 1040,
871 + },
872 + actions: [
873 + {
874 + type: 'uri',
875 + linkUri: 'https://example.com/',
876 + area: {
877 + x: 0,
878 + y: 0,
879 + width: 520,
880 + height: 1040,
881 + },
882 + },
883 + {
884 + type: 'message',
885 + text: 'hello',
886 + area: {
887 + x: 520,
888 + y: 0,
889 + width: 520,
890 + height: 1040,
891 + },
892 + },
893 + ],
894 + },
895 + ],
896 + });
897 + expect(config.headers).toEqual(headers);
898 + return [200, reply];
899 + });
900 +
901 + const res = await client.multicastImagemap(
902 + [RECIPIENT_ID],
903 + 'this is an imagemap',
904 + {
905 + baseUrl: 'https://example.com/bot/images/rm001',
906 + baseHeight: 1040,
907 + baseWidth: 1040,
908 + actions: [
909 + {
910 + type: 'uri',
911 + linkUri: 'https://example.com/',
912 + area: {
913 + x: 0,
914 + y: 0,
915 + width: 520,
916 + height: 1040,
917 + },
918 + },
919 + {
920 + type: 'message',
921 + text: 'hello',
922 + area: {
923 + x: 520,
924 + y: 0,
925 + width: 520,
926 + height: 1040,
927 + },
928 + },
929 + ],
930 + },
931 + { accessToken: CUSTOM_ACCESS_TOKEN }
932 + );
933 +
934 + expect(res).toEqual(reply);
935 + });
936 +
937 + it('should support baseSize argument', async () => {
938 + expect.assertions(4);
939 +
940 + const { client, mock, headers } = createMock();
941 +
942 + const reply = {};
943 +
944 + mock.onPost().reply(config => {
945 + expect(config.url).toEqual(
946 + 'https://api.line.me/v2/bot/message/multicast'
947 + );
948 + expect(JSON.parse(config.data)).toEqual({
949 + to: [RECIPIENT_ID],
950 + messages: [
951 + {
952 + type: 'imagemap',
953 + baseUrl: 'https://example.com/bot/images/rm001',
954 + altText: 'this is an imagemap',
955 + baseSize: {
956 + height: 1040,
957 + width: 1040,
958 + },
959 + actions: [
960 + {
961 + type: 'uri',
962 + linkUri: 'https://example.com/',
963 + area: {
964 + x: 0,
965 + y: 0,
966 + width: 520,
967 + height: 1040,
968 + },
969 + },
970 + {
971 + type: 'message',
972 + text: 'hello',
973 + area: {
974 + x: 520,
975 + y: 0,
976 + width: 520,
977 + height: 1040,
978 + },
979 + },
980 + ],
981 + },
982 + ],
983 + });
984 + expect(config.headers).toEqual(headers);
985 + return [200, reply];
986 + });
987 +
988 + const res = await client.multicastImagemap(
989 + [RECIPIENT_ID],
990 + 'this is an imagemap',
991 + {
992 + baseUrl: 'https://example.com/bot/images/rm001',
993 + baseSize: {
994 + height: 1040,
995 + width: 1040,
996 + },
997 + actions: [
998 + {
999 + type: 'uri',
1000 + linkUri: 'https://example.com/',
1001 + area: {
1002 + x: 0,
1003 + y: 0,
1004 + width: 520,
1005 + height: 1040,
1006 + },
1007 + },
1008 + {
1009 + type: 'message',
1010 + text: 'hello',
1011 + area: {
1012 + x: 520,
1013 + y: 0,
1014 + width: 520,
1015 + height: 1040,
1016 + },
1017 + },
1018 + ],
1019 + }
1020 + );
1021 +
1022 + expect(res).toEqual(reply);
1023 + });
1024 +
1025 + it('should support video', async () => {
1026 + expect.assertions(4);
1027 +
1028 + const { client, mock, headers } = createMock();
1029 +
1030 + const reply = {};
1031 +
1032 + mock.onPost().reply(config => {
1033 + expect(config.url).toEqual(
1034 + 'https://api.line.me/v2/bot/message/multicast'
1035 + );
1036 + expect(JSON.parse(config.data)).toEqual({
1037 + to: [RECIPIENT_ID],
1038 + messages: [
1039 + {
1040 + type: 'imagemap',
1041 + baseUrl: 'https://example.com/bot/images/rm001',
1042 + altText: 'this is an imagemap',
1043 + baseSize: {
1044 + height: 1040,
1045 + width: 1040,
1046 + },
1047 + video: {
1048 + originalContentUrl: 'https://example.com/video.mp4',
1049 + previewImageUrl: 'https://example.com/video_preview.jpg',
1050 + area: {
1051 + x: 0,
1052 + y: 0,
1053 + width: 1040,
1054 + height: 585,
1055 + },
1056 + externalLink: {
1057 + linkUri: 'https://example.com/see_more.html',
1058 + label: 'See More',
1059 + },
1060 + },
1061 + actions: [
1062 + {
1063 + type: 'uri',
1064 + linkUri: 'https://example.com/',
1065 + area: {
1066 + x: 0,
1067 + y: 0,
1068 + width: 520,
1069 + height: 1040,
1070 + },
1071 + },
1072 + {
1073 + type: 'message',
1074 + text: 'hello',
1075 + area: {
1076 + x: 520,
1077 + y: 0,
1078 + width: 520,
1079 + height: 1040,
1080 + },
1081 + },
1082 + ],
1083 + },
1084 + ],
1085 + });
1086 + expect(config.headers).toEqual(headers);
1087 + return [200, reply];
1088 + });
1089 +
1090 + const res = await client.multicastImagemap(
1091 + [RECIPIENT_ID],
1092 + 'this is an imagemap',
1093 + {
1094 + baseUrl: 'https://example.com/bot/images/rm001',
1095 + baseSize: {
1096 + height: 1040,
1097 + width: 1040,
1098 + },
1099 + video: {
1100 + originalContentUrl: 'https://example.com/video.mp4',
1101 + previewImageUrl: 'https://example.com/video_preview.jpg',
1102 + area: {
1103 + x: 0,
1104 + y: 0,
1105 + width: 1040,
1106 + height: 585,
1107 + },
1108 + externalLink: {
1109 + linkUri: 'https://example.com/see_more.html',
1110 + label: 'See More',
1111 + },
1112 + },
1113 + actions: [
1114 + {
1115 + type: 'uri',
1116 + linkUri: 'https://example.com/',
1117 + area: {
1118 + x: 0,
1119 + y: 0,
1120 + width: 520,
1121 + height: 1040,
1122 + },
1123 + },
1124 + {
1125 + type: 'message',
1126 + text: 'hello',
1127 + area: {
1128 + x: 520,
1129 + y: 0,
1130 + width: 520,
1131 + height: 1040,
1132 + },
1133 + },
1134 + ],
1135 + }
1136 + );
1137 +
1138 + expect(res).toEqual(reply);
1139 + });
1140 + });
1141 +
1142 + describe('#multicastTemplate', () => {
1143 + it('should call multicast api', async () => {
1144 + expect.assertions(4);
1145 +
1146 + const { client, mock, headers } = createMock();
1147 +
1148 + const reply = {};
1149 +
1150 + mock.onPost().reply(config => {
1151 + expect(config.url).toEqual(
1152 + 'https://api.line.me/v2/bot/message/multicast'
1153 + );
1154 + expect(JSON.parse(config.data)).toEqual({
1155 + to: [RECIPIENT_ID],
1156 + messages: [
1157 + {
1158 + type: 'template',
1159 + altText: 'this is a template',
1160 + template: {
1161 + type: 'buttons',
1162 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1163 + title: 'Menu',
1164 + text: 'Please select',
1165 + actions: [
1166 + {
1167 + type: 'postback',
1168 + label: 'Buy',
1169 + data: 'action=buy&itemid=123',
1170 + },
1171 + {
1172 + type: 'postback',
1173 + label: 'Add to cart',
1174 + data: 'action=add&itemid=123',
1175 + },
1176 + {
1177 + type: 'uri',
1178 + label: 'View detail',
1179 + uri: 'http://example.com/page/123',
1180 + },
1181 + ],
1182 + },
1183 + },
1184 + ],
1185 + });
1186 +
1187 + expect(config.headers).toEqual(headers);
1188 + return [200, reply];
1189 + });
1190 +
1191 + const res = await client.multicastTemplate(
1192 + [RECIPIENT_ID],
1193 + 'this is a template',
1194 + {
1195 + type: 'buttons',
1196 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1197 + title: 'Menu',
1198 + text: 'Please select',
1199 + actions: [
1200 + {
1201 + type: 'postback',
1202 + label: 'Buy',
1203 + data: 'action=buy&itemid=123',
1204 + },
1205 + {
1206 + type: 'postback',
1207 + label: 'Add to cart',
1208 + data: 'action=add&itemid=123',
1209 + },
1210 + {
1211 + type: 'uri',
1212 + label: 'View detail',
1213 + uri: 'http://example.com/page/123',
1214 + },
1215 + ],
1216 + }
1217 + );
1218 +
1219 + expect(res).toEqual(reply);
1220 + });
1221 +
1222 + it('should work with custom access token', async () => {
1223 + expect.assertions(4);
1224 +
1225 + const { client, mock, headers } = createMock({
1226 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1227 + });
1228 +
1229 + const reply = {};
1230 +
1231 + mock.onPost().reply(config => {
1232 + expect(config.url).toEqual(
1233 + 'https://api.line.me/v2/bot/message/multicast'
1234 + );
1235 + expect(JSON.parse(config.data)).toEqual({
1236 + to: [RECIPIENT_ID],
1237 + messages: [
1238 + {
1239 + type: 'template',
1240 + altText: 'this is a template',
1241 + template: {
1242 + type: 'buttons',
1243 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1244 + title: 'Menu',
1245 + text: 'Please select',
1246 + actions: [
1247 + {
1248 + type: 'postback',
1249 + label: 'Buy',
1250 + data: 'action=buy&itemid=123',
1251 + },
1252 + {
1253 + type: 'postback',
1254 + label: 'Add to cart',
1255 + data: 'action=add&itemid=123',
1256 + },
1257 + {
1258 + type: 'uri',
1259 + label: 'View detail',
1260 + uri: 'http://example.com/page/123',
1261 + },
1262 + ],
1263 + },
1264 + },
1265 + ],
1266 + });
1267 +
1268 + expect(config.headers).toEqual(headers);
1269 + return [200, reply];
1270 + });
1271 +
1272 + const res = await client.multicastTemplate(
1273 + [RECIPIENT_ID],
1274 + 'this is a template',
1275 + {
1276 + type: 'buttons',
1277 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1278 + title: 'Menu',
1279 + text: 'Please select',
1280 + actions: [
1281 + {
1282 + type: 'postback',
1283 + label: 'Buy',
1284 + data: 'action=buy&itemid=123',
1285 + },
1286 + {
1287 + type: 'postback',
1288 + label: 'Add to cart',
1289 + data: 'action=add&itemid=123',
1290 + },
1291 + {
1292 + type: 'uri',
1293 + label: 'View detail',
1294 + uri: 'http://example.com/page/123',
1295 + },
1296 + ],
1297 + },
1298 + { accessToken: CUSTOM_ACCESS_TOKEN }
1299 + );
1300 +
1301 + expect(res).toEqual(reply);
1302 + });
1303 + });
1304 +
1305 + describe('#multicastButtonTemplate', () => {
1306 + it('should call multicast api', async () => {
1307 + expect.assertions(4);
1308 +
1309 + const { client, mock, headers } = createMock();
1310 +
1311 + const reply = {};
1312 +
1313 + mock.onPost().reply(config => {
1314 + expect(config.url).toEqual(
1315 + 'https://api.line.me/v2/bot/message/multicast'
1316 + );
1317 + expect(JSON.parse(config.data)).toEqual({
1318 + to: [RECIPIENT_ID],
1319 + messages: [
1320 + {
1321 + type: 'template',
1322 + altText: 'this is a template',
1323 + template: {
1324 + type: 'buttons',
1325 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1326 + imageAspectRatio: 'rectangle',
1327 + imageSize: 'cover',
1328 + imageBackgroundColor: '#FFFFFF',
1329 + title: 'Menu',
1330 + text: 'Please select',
1331 + defaultAction: {
1332 + type: 'uri',
1333 + label: 'View detail',
1334 + uri: 'http://example.com/page/123',
1335 + },
1336 + actions: [
1337 + {
1338 + type: 'postback',
1339 + label: 'Buy',
1340 + data: 'action=buy&itemid=123',
1341 + },
1342 + {
1343 + type: 'postback',
1344 + label: 'Add to cart',
1345 + data: 'action=add&itemid=123',
1346 + },
1347 + {
1348 + type: 'uri',
1349 + label: 'View detail',
1350 + uri: 'http://example.com/page/123',
1351 + },
1352 + ],
1353 + },
1354 + },
1355 + ],
1356 + });
1357 + expect(config.headers).toEqual(headers);
1358 + return [200, reply];
1359 + });
1360 +
1361 + const res = await client.multicastButtonTemplate(
1362 + [RECIPIENT_ID],
1363 + 'this is a template',
1364 + {
1365 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1366 + imageAspectRatio: 'rectangle',
1367 + imageSize: 'cover',
1368 + imageBackgroundColor: '#FFFFFF',
1369 + title: 'Menu',
1370 + text: 'Please select',
1371 + defaultAction: {
1372 + type: 'uri',
1373 + label: 'View detail',
1374 + uri: 'http://example.com/page/123',
1375 + },
1376 + actions: [
1377 + {
1378 + type: 'postback',
1379 + label: 'Buy',
1380 + data: 'action=buy&itemid=123',
1381 + },
1382 + {
1383 + type: 'postback',
1384 + label: 'Add to cart',
1385 + data: 'action=add&itemid=123',
1386 + },
1387 + {
1388 + type: 'uri',
1389 + label: 'View detail',
1390 + uri: 'http://example.com/page/123',
1391 + },
1392 + ],
1393 + }
1394 + );
1395 +
1396 + expect(res).toEqual(reply);
1397 + });
1398 +
1399 + it('should work with custom access token', async () => {
1400 + expect.assertions(4);
1401 +
1402 + const { client, mock, headers } = createMock({
1403 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1404 + });
1405 +
1406 + const reply = {};
1407 +
1408 + mock.onPost().reply(config => {
1409 + expect(config.url).toEqual(
1410 + 'https://api.line.me/v2/bot/message/multicast'
1411 + );
1412 + expect(JSON.parse(config.data)).toEqual({
1413 + to: [RECIPIENT_ID],
1414 + messages: [
1415 + {
1416 + type: 'template',
1417 + altText: 'this is a template',
1418 + template: {
1419 + type: 'buttons',
1420 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1421 + imageAspectRatio: 'rectangle',
1422 + imageSize: 'cover',
1423 + imageBackgroundColor: '#FFFFFF',
1424 + title: 'Menu',
1425 + text: 'Please select',
1426 + defaultAction: {
1427 + type: 'uri',
1428 + label: 'View detail',
1429 + uri: 'http://example.com/page/123',
1430 + },
1431 + actions: [
1432 + {
1433 + type: 'postback',
1434 + label: 'Buy',
1435 + data: 'action=buy&itemid=123',
1436 + },
1437 + {
1438 + type: 'postback',
1439 + label: 'Add to cart',
1440 + data: 'action=add&itemid=123',
1441 + },
1442 + {
1443 + type: 'uri',
1444 + label: 'View detail',
1445 + uri: 'http://example.com/page/123',
1446 + },
1447 + ],
1448 + },
1449 + },
1450 + ],
1451 + });
1452 + expect(config.headers).toEqual(headers);
1453 + return [200, reply];
1454 + });
1455 +
1456 + const res = await client.multicastButtonTemplate(
1457 + [RECIPIENT_ID],
1458 + 'this is a template',
1459 + {
1460 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1461 + imageAspectRatio: 'rectangle',
1462 + imageSize: 'cover',
1463 + imageBackgroundColor: '#FFFFFF',
1464 + title: 'Menu',
1465 + text: 'Please select',
1466 + defaultAction: {
1467 + type: 'uri',
1468 + label: 'View detail',
1469 + uri: 'http://example.com/page/123',
1470 + },
1471 + actions: [
1472 + {
1473 + type: 'postback',
1474 + label: 'Buy',
1475 + data: 'action=buy&itemid=123',
1476 + },
1477 + {
1478 + type: 'postback',
1479 + label: 'Add to cart',
1480 + data: 'action=add&itemid=123',
1481 + },
1482 + {
1483 + type: 'uri',
1484 + label: 'View detail',
1485 + uri: 'http://example.com/page/123',
1486 + },
1487 + ],
1488 + },
1489 + { accessToken: CUSTOM_ACCESS_TOKEN }
1490 + );
1491 +
1492 + expect(res).toEqual(reply);
1493 + });
1494 + });
1495 +
1496 + describe('#multicastConfirmTemplate', () => {
1497 + it('should call multicast api', async () => {
1498 + expect.assertions(4);
1499 +
1500 + const { client, mock, headers } = createMock();
1501 +
1502 + const reply = {};
1503 +
1504 + mock.onPost().reply(config => {
1505 + expect(config.url).toEqual(
1506 + 'https://api.line.me/v2/bot/message/multicast'
1507 + );
1508 + expect(JSON.parse(config.data)).toEqual({
1509 + to: [RECIPIENT_ID],
1510 + messages: [
1511 + {
1512 + type: 'template',
1513 + altText: 'this is a confirm template',
1514 + template: {
1515 + type: 'confirm',
1516 + text: 'Are you sure?',
1517 + actions: [
1518 + {
1519 + type: 'message',
1520 + label: 'Yes',
1521 + text: 'yes',
1522 + },
1523 + {
1524 + type: 'message',
1525 + label: 'No',
1526 + text: 'no',
1527 + },
1528 + ],
1529 + },
1530 + },
1531 + ],
1532 + });
1533 + expect(config.headers).toEqual(headers);
1534 + return [200, reply];
1535 + });
1536 +
1537 + const res = await client.multicastConfirmTemplate(
1538 + [RECIPIENT_ID],
1539 + 'this is a confirm template',
1540 + {
1541 + text: 'Are you sure?',
1542 + actions: [
1543 + {
1544 + type: 'message',
1545 + label: 'Yes',
1546 + text: 'yes',
1547 + },
1548 + {
1549 + type: 'message',
1550 + label: 'No',
1551 + text: 'no',
1552 + },
1553 + ],
1554 + }
1555 + );
1556 +
1557 + expect(res).toEqual(reply);
1558 + });
1559 +
1560 + it('should work with custom access token', async () => {
1561 + expect.assertions(4);
1562 +
1563 + const { client, mock, headers } = createMock({
1564 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1565 + });
1566 +
1567 + const reply = {};
1568 +
1569 + mock.onPost().reply(config => {
1570 + expect(config.url).toEqual(
1571 + 'https://api.line.me/v2/bot/message/multicast'
1572 + );
1573 + expect(JSON.parse(config.data)).toEqual({
1574 + to: [RECIPIENT_ID],
1575 + messages: [
1576 + {
1577 + type: 'template',
1578 + altText: 'this is a confirm template',
1579 + template: {
1580 + type: 'confirm',
1581 + text: 'Are you sure?',
1582 + actions: [
1583 + {
1584 + type: 'message',
1585 + label: 'Yes',
1586 + text: 'yes',
1587 + },
1588 + {
1589 + type: 'message',
1590 + label: 'No',
1591 + text: 'no',
1592 + },
1593 + ],
1594 + },
1595 + },
1596 + ],
1597 + });
1598 + expect(config.headers).toEqual(headers);
1599 + return [200, reply];
1600 + });
1601 +
1602 + const res = await client.multicastConfirmTemplate(
1603 + [RECIPIENT_ID],
1604 + 'this is a confirm template',
1605 + {
1606 + text: 'Are you sure?',
1607 + actions: [
1608 + {
1609 + type: 'message',
1610 + label: 'Yes',
1611 + text: 'yes',
1612 + },
1613 + {
1614 + type: 'message',
1615 + label: 'No',
1616 + text: 'no',
1617 + },
1618 + ],
1619 + },
1620 + { accessToken: CUSTOM_ACCESS_TOKEN }
1621 + );
1622 +
1623 + expect(res).toEqual(reply);
1624 + });
1625 + });
1626 +
1627 + describe('#multicastCarouselTemplate', () => {
1628 + it('should call multicast api', async () => {
1629 + expect.assertions(4);
1630 +
1631 + const { client, mock, headers } = createMock();
1632 +
1633 + const reply = {};
1634 +
1635 + mock.onPost().reply(config => {
1636 + expect(config.url).toEqual(
1637 + 'https://api.line.me/v2/bot/message/multicast'
1638 + );
1639 + expect(JSON.parse(config.data)).toEqual({
1640 + to: [RECIPIENT_ID],
1641 + messages: [
1642 + {
1643 + type: 'template',
1644 + altText: 'this is a carousel template',
1645 + template: {
1646 + type: 'carousel',
1647 + imageAspectRatio: 'rectangle',
1648 + imageSize: 'cover',
1649 + columns: [
1650 + {
1651 + thumbnailImageUrl:
1652 + 'https://example.com/bot/images/item1.jpg',
1653 + title: 'this is menu',
1654 + text: 'description',
1655 + actions: [
1656 + {
1657 + type: 'postback',
1658 + label: 'Buy',
1659 + data: 'action=buy&itemid=111',
1660 + },
1661 + {
1662 + type: 'postback',
1663 + label: 'Add to cart',
1664 + data: 'action=add&itemid=111',
1665 + },
1666 + {
1667 + type: 'uri',
1668 + label: 'View detail',
1669 + uri: 'http://example.com/page/111',
1670 + },
1671 + ],
1672 + },
1673 + {
1674 + thumbnailImageUrl:
1675 + 'https://example.com/bot/images/item2.jpg',
1676 + title: 'this is menu',
1677 + text: 'description',
1678 + actions: [
1679 + {
1680 + type: 'postback',
1681 + label: 'Buy',
1682 + data: 'action=buy&itemid=222',
1683 + },
1684 + {
1685 + type: 'postback',
1686 + label: 'Add to cart',
1687 + data: 'action=add&itemid=222',
1688 + },
1689 + {
1690 + type: 'uri',
1691 + label: 'View detail',
1692 + uri: 'http://example.com/page/222',
1693 + },
1694 + ],
1695 + },
1696 + ],
1697 + },
1698 + },
1699 + ],
1700 + });
1701 + expect(config.headers).toEqual(headers);
1702 + return [200, reply];
1703 + });
1704 +
1705 + const res = await client.multicastCarouselTemplate(
1706 + [RECIPIENT_ID],
1707 + 'this is a carousel template',
1708 + [
1709 + {
1710 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1711 + title: 'this is menu',
1712 + text: 'description',
1713 + actions: [
1714 + {
1715 + type: 'postback',
1716 + label: 'Buy',
1717 + data: 'action=buy&itemid=111',
1718 + },
1719 + {
1720 + type: 'postback',
1721 + label: 'Add to cart',
1722 + data: 'action=add&itemid=111',
1723 + },
1724 + {
1725 + type: 'uri',
1726 + label: 'View detail',
1727 + uri: 'http://example.com/page/111',
1728 + },
1729 + ],
1730 + },
1731 + {
1732 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1733 + title: 'this is menu',
1734 + text: 'description',
1735 + actions: [
1736 + {
1737 + type: 'postback',
1738 + label: 'Buy',
1739 + data: 'action=buy&itemid=222',
1740 + },
1741 + {
1742 + type: 'postback',
1743 + label: 'Add to cart',
1744 + data: 'action=add&itemid=222',
1745 + },
1746 + {
1747 + type: 'uri',
1748 + label: 'View detail',
1749 + uri: 'http://example.com/page/222',
1750 + },
1751 + ],
1752 + },
1753 + ],
1754 + {
1755 + imageAspectRatio: 'rectangle',
1756 + imageSize: 'cover',
1757 + }
1758 + );
1759 +
1760 + expect(res).toEqual(reply);
1761 + });
1762 +
1763 + it('should work with custom access token', async () => {
1764 + expect.assertions(4);
1765 +
1766 + const { client, mock, headers } = createMock({
1767 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1768 + });
1769 +
1770 + const reply = {};
1771 +
1772 + mock.onPost().reply(config => {
1773 + expect(config.url).toEqual(
1774 + 'https://api.line.me/v2/bot/message/multicast'
1775 + );
1776 + expect(JSON.parse(config.data)).toEqual({
1777 + to: [RECIPIENT_ID],
1778 + messages: [
1779 + {
1780 + type: 'template',
1781 + altText: 'this is a carousel template',
1782 + template: {
1783 + type: 'carousel',
1784 + imageAspectRatio: 'rectangle',
1785 + imageSize: 'cover',
1786 + columns: [
1787 + {
1788 + thumbnailImageUrl:
1789 + 'https://example.com/bot/images/item1.jpg',
1790 + title: 'this is menu',
1791 + text: 'description',
1792 + actions: [
1793 + {
1794 + type: 'postback',
1795 + label: 'Buy',
1796 + data: 'action=buy&itemid=111',
1797 + },
1798 + {
1799 + type: 'postback',
1800 + label: 'Add to cart',
1801 + data: 'action=add&itemid=111',
1802 + },
1803 + {
1804 + type: 'uri',
1805 + label: 'View detail',
1806 + uri: 'http://example.com/page/111',
1807 + },
1808 + ],
1809 + },
1810 + {
1811 + thumbnailImageUrl:
1812 + 'https://example.com/bot/images/item2.jpg',
1813 + title: 'this is menu',
1814 + text: 'description',
1815 + actions: [
1816 + {
1817 + type: 'postback',
1818 + label: 'Buy',
1819 + data: 'action=buy&itemid=222',
1820 + },
1821 + {
1822 + type: 'postback',
1823 + label: 'Add to cart',
1824 + data: 'action=add&itemid=222',
1825 + },
1826 + {
1827 + type: 'uri',
1828 + label: 'View detail',
1829 + uri: 'http://example.com/page/222',
1830 + },
1831 + ],
1832 + },
1833 + ],
1834 + },
1835 + },
1836 + ],
1837 + });
1838 + expect(config.headers).toEqual(headers);
1839 + return [200, reply];
1840 + });
1841 +
1842 + const res = await client.multicastCarouselTemplate(
1843 + [RECIPIENT_ID],
1844 + 'this is a carousel template',
1845 + [
1846 + {
1847 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1848 + title: 'this is menu',
1849 + text: 'description',
1850 + actions: [
1851 + {
1852 + type: 'postback',
1853 + label: 'Buy',
1854 + data: 'action=buy&itemid=111',
1855 + },
1856 + {
1857 + type: 'postback',
1858 + label: 'Add to cart',
1859 + data: 'action=add&itemid=111',
1860 + },
1861 + {
1862 + type: 'uri',
1863 + label: 'View detail',
1864 + uri: 'http://example.com/page/111',
1865 + },
1866 + ],
1867 + },
1868 + {
1869 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1870 + title: 'this is menu',
1871 + text: 'description',
1872 + actions: [
1873 + {
1874 + type: 'postback',
1875 + label: 'Buy',
1876 + data: 'action=buy&itemid=222',
1877 + },
1878 + {
1879 + type: 'postback',
1880 + label: 'Add to cart',
1881 + data: 'action=add&itemid=222',
1882 + },
1883 + {
1884 + type: 'uri',
1885 + label: 'View detail',
1886 + uri: 'http://example.com/page/222',
1887 + },
1888 + ],
1889 + },
1890 + ],
1891 + {
1892 + imageAspectRatio: 'rectangle',
1893 + imageSize: 'cover',
1894 + accessToken: CUSTOM_ACCESS_TOKEN,
1895 + }
1896 + );
1897 +
1898 + expect(res).toEqual(reply);
1899 + });
1900 +
1901 + it('should work without option', async () => {
1902 + expect.assertions(4);
1903 +
1904 + const { client, mock, headers } = createMock();
1905 +
1906 + const reply = {};
1907 +
1908 + mock.onPost().reply(config => {
1909 + expect(config.url).toEqual(
1910 + 'https://api.line.me/v2/bot/message/multicast'
1911 + );
1912 + expect(JSON.parse(config.data)).toEqual({
1913 + to: [RECIPIENT_ID],
1914 + messages: [
1915 + {
1916 + type: 'template',
1917 + altText: 'this is a carousel template',
1918 + template: {
1919 + type: 'carousel',
1920 + columns: [
1921 + {
1922 + thumbnailImageUrl:
1923 + 'https://example.com/bot/images/item1.jpg',
1924 + title: 'this is menu',
1925 + text: 'description',
1926 + actions: [
1927 + {
1928 + type: 'postback',
1929 + label: 'Buy',
1930 + data: 'action=buy&itemid=111',
1931 + },
1932 + {
1933 + type: 'postback',
1934 + label: 'Add to cart',
1935 + data: 'action=add&itemid=111',
1936 + },
1937 + {
1938 + type: 'uri',
1939 + label: 'View detail',
1940 + uri: 'http://example.com/page/111',
1941 + },
1942 + ],
1943 + },
1944 + {
1945 + thumbnailImageUrl:
1946 + 'https://example.com/bot/images/item2.jpg',
1947 + title: 'this is menu',
1948 + text: 'description',
1949 + actions: [
1950 + {
1951 + type: 'postback',
1952 + label: 'Buy',
1953 + data: 'action=buy&itemid=222',
1954 + },
1955 + {
1956 + type: 'postback',
1957 + label: 'Add to cart',
1958 + data: 'action=add&itemid=222',
1959 + },
1960 + {
1961 + type: 'uri',
1962 + label: 'View detail',
1963 + uri: 'http://example.com/page/222',
1964 + },
1965 + ],
1966 + },
1967 + ],
1968 + },
1969 + },
1970 + ],
1971 + });
1972 + expect(config.headers).toEqual(headers);
1973 + return [200, reply];
1974 + });
1975 +
1976 + const res = await client.multicastCarouselTemplate(
1977 + [RECIPIENT_ID],
1978 + 'this is a carousel template',
1979 + [
1980 + {
1981 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1982 + title: 'this is menu',
1983 + text: 'description',
1984 + actions: [
1985 + {
1986 + type: 'postback',
1987 + label: 'Buy',
1988 + data: 'action=buy&itemid=111',
1989 + },
1990 + {
1991 + type: 'postback',
1992 + label: 'Add to cart',
1993 + data: 'action=add&itemid=111',
1994 + },
1995 + {
1996 + type: 'uri',
1997 + label: 'View detail',
1998 + uri: 'http://example.com/page/111',
1999 + },
2000 + ],
2001 + },
2002 + {
2003 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
2004 + title: 'this is menu',
2005 + text: 'description',
2006 + actions: [
2007 + {
2008 + type: 'postback',
2009 + label: 'Buy',
2010 + data: 'action=buy&itemid=222',
2011 + },
2012 + {
2013 + type: 'postback',
2014 + label: 'Add to cart',
2015 + data: 'action=add&itemid=222',
2016 + },
2017 + {
2018 + type: 'uri',
2019 + label: 'View detail',
2020 + uri: 'http://example.com/page/222',
2021 + },
2022 + ],
2023 + },
2024 + ]
2025 + );
2026 +
2027 + expect(res).toEqual(reply);
2028 + });
2029 + });
2030 +
2031 + describe('#multicastImageCarouselTemplate', () => {
2032 + it('should call multicast api', async () => {
2033 + expect.assertions(4);
2034 +
2035 + const { client, mock, headers } = createMock();
2036 +
2037 + const reply = {};
2038 +
2039 + mock.onPost().reply(config => {
2040 + expect(config.url).toEqual(
2041 + 'https://api.line.me/v2/bot/message/multicast'
2042 + );
2043 + expect(JSON.parse(config.data)).toEqual({
2044 + to: [RECIPIENT_ID],
2045 + messages: [
2046 + {
2047 + type: 'template',
2048 + altText: 'this is an image carousel template',
2049 + template: {
2050 + type: 'image_carousel',
2051 + columns: [
2052 + {
2053 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2054 + action: {
2055 + type: 'postback',
2056 + label: 'Buy',
2057 + data: 'action=buy&itemid=111',
2058 + },
2059 + },
2060 + {
2061 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2062 + action: {
2063 + type: 'message',
2064 + label: 'Yes',
2065 + text: 'yes',
2066 + },
2067 + },
2068 + {
2069 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2070 + action: {
2071 + type: 'uri',
2072 + label: 'View detail',
2073 + uri: 'http://example.com/page/222',
2074 + },
2075 + },
2076 + ],
2077 + },
2078 + },
2079 + ],
2080 + });
2081 + expect(config.headers).toEqual(headers);
2082 + return [200, reply];
2083 + });
2084 +
2085 + const res = await client.multicastImageCarouselTemplate(
2086 + [RECIPIENT_ID],
2087 + 'this is an image carousel template',
2088 + [
2089 + {
2090 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2091 + action: {
2092 + type: 'postback',
2093 + label: 'Buy',
2094 + data: 'action=buy&itemid=111',
2095 + },
2096 + },
2097 + {
2098 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2099 + action: {
2100 + type: 'message',
2101 + label: 'Yes',
2102 + text: 'yes',
2103 + },
2104 + },
2105 + {
2106 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2107 + action: {
2108 + type: 'uri',
2109 + label: 'View detail',
2110 + uri: 'http://example.com/page/222',
2111 + },
2112 + },
2113 + ]
2114 + );
2115 +
2116 + expect(res).toEqual(reply);
2117 + });
2118 +
2119 + it('should work with custom access token', async () => {
2120 + expect.assertions(4);
2121 +
2122 + const { client, mock, headers } = createMock({
2123 + customAccessToken: CUSTOM_ACCESS_TOKEN,
2124 + });
2125 +
2126 + const reply = {};
2127 +
2128 + mock.onPost().reply(config => {
2129 + expect(config.url).toEqual(
2130 + 'https://api.line.me/v2/bot/message/multicast'
2131 + );
2132 + expect(JSON.parse(config.data)).toEqual({
2133 + to: [RECIPIENT_ID],
2134 + messages: [
2135 + {
2136 + type: 'template',
2137 + altText: 'this is an image carousel template',
2138 + template: {
2139 + type: 'image_carousel',
2140 + columns: [
2141 + {
2142 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2143 + action: {
2144 + type: 'postback',
2145 + label: 'Buy',
2146 + data: 'action=buy&itemid=111',
2147 + },
2148 + },
2149 + {
2150 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2151 + action: {
2152 + type: 'message',
2153 + label: 'Yes',
2154 + text: 'yes',
2155 + },
2156 + },
2157 + {
2158 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2159 + action: {
2160 + type: 'uri',
2161 + label: 'View detail',
2162 + uri: 'http://example.com/page/222',
2163 + },
2164 + },
2165 + ],
2166 + },
2167 + },
2168 + ],
2169 + });
2170 + expect(config.headers).toEqual(headers);
2171 + return [200, reply];
2172 + });
2173 +
2174 + const res = await client.multicastImageCarouselTemplate(
2175 + [RECIPIENT_ID],
2176 + 'this is an image carousel template',
2177 + [
2178 + {
2179 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2180 + action: {
2181 + type: 'postback',
2182 + label: 'Buy',
2183 + data: 'action=buy&itemid=111',
2184 + },
2185 + },
2186 + {
2187 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2188 + action: {
2189 + type: 'message',
2190 + label: 'Yes',
2191 + text: 'yes',
2192 + },
2193 + },
2194 + {
2195 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2196 + action: {
2197 + type: 'uri',
2198 + label: 'View detail',
2199 + uri: 'http://example.com/page/222',
2200 + },
2201 + },
2202 + ],
2203 + { accessToken: CUSTOM_ACCESS_TOKEN }
2204 + );
2205 +
2206 + expect(res).toEqual(reply);
2207 + });
2208 + });
2209 +
2210 + describe('#multicastFlex', () => {
2211 + it('should call multicast api', async () => {
2212 + expect.assertions(4);
2213 +
2214 + const { client, mock, headers } = createMock();
2215 +
2216 + const reply = {};
2217 +
2218 + mock.onPost().reply(config => {
2219 + expect(config.url).toEqual(
2220 + 'https://api.line.me/v2/bot/message/multicast'
2221 + );
2222 + expect(JSON.parse(config.data)).toEqual({
2223 + to: [RECIPIENT_ID],
2224 + messages: [
2225 + {
2226 + type: 'flex',
2227 + altText: 'this is a flex message',
2228 + contents: {
2229 + type: 'bubble',
2230 + header: {
2231 + type: 'box',
2232 + layout: 'vertical',
2233 + contents: [
2234 + {
2235 + type: 'text',
2236 + text: 'Header text',
2237 + },
2238 + ],
2239 + },
2240 + hero: {
2241 + type: 'image',
2242 + url: 'https://example.com/flex/images/image.jpg',
2243 + },
2244 + body: {
2245 + type: 'box',
2246 + layout: 'vertical',
2247 + contents: [
2248 + {
2249 + type: 'text',
2250 + text: 'Body text',
2251 + },
2252 + ],
2253 + },
2254 + footer: {
2255 + type: 'box',
2256 + layout: 'vertical',
2257 + contents: [
2258 + {
2259 + type: 'text',
2260 + text: 'Footer text',
2261 + },
2262 + ],
2263 + },
2264 + styles: {
2265 + comment: 'See the example of a bubble style object',
2266 + },
2267 + },
2268 + },
2269 + ],
2270 + });
2271 + expect(config.headers).toEqual(headers);
2272 + return [200, reply];
2273 + });
2274 +
2275 + const res = await client.multicastFlex(
2276 + [RECIPIENT_ID],
2277 + 'this is a flex message',
2278 + {
2279 + type: 'bubble',
2280 + header: {
2281 + type: 'box',
2282 + layout: 'vertical',
2283 + contents: [
2284 + {
2285 + type: 'text',
2286 + text: 'Header text',
2287 + },
2288 + ],
2289 + },
2290 + hero: {
2291 + type: 'image',
2292 + url: 'https://example.com/flex/images/image.jpg',
2293 + },
2294 + body: {
2295 + type: 'box',
2296 + layout: 'vertical',
2297 + contents: [
2298 + {
2299 + type: 'text',
2300 + text: 'Body text',
2301 + },
2302 + ],
2303 + },
2304 + footer: {
2305 + type: 'box',
2306 + layout: 'vertical',
2307 + contents: [
2308 + {
2309 + type: 'text',
2310 + text: 'Footer text',
2311 + },
2312 + ],
2313 + },
2314 + styles: {
2315 + comment: 'See the example of a bubble style object',
2316 + },
2317 + }
2318 + );
2319 +
2320 + expect(res).toEqual(reply);
2321 + });
2322 +
2323 + it('should work with custom access token', async () => {
2324 + expect.assertions(4);
2325 +
2326 + const { client, mock, headers } = createMock({
2327 + customAccessToken: CUSTOM_ACCESS_TOKEN,
2328 + });
2329 +
2330 + const reply = {};
2331 +
2332 + mock.onPost().reply(config => {
2333 + expect(config.url).toEqual(
2334 + 'https://api.line.me/v2/bot/message/multicast'
2335 + );
2336 + expect(JSON.parse(config.data)).toEqual({
2337 + to: [RECIPIENT_ID],
2338 + messages: [
2339 + {
2340 + type: 'flex',
2341 + altText: 'this is a flex message',
2342 + contents: {
2343 + type: 'bubble',
2344 + header: {
2345 + type: 'box',
2346 + layout: 'vertical',
2347 + contents: [
2348 + {
2349 + type: 'text',
2350 + text: 'Header text',
2351 + },
2352 + ],
2353 + },
2354 + hero: {
2355 + type: 'image',
2356 + url: 'https://example.com/flex/images/image.jpg',
2357 + },
2358 + body: {
2359 + type: 'box',
2360 + layout: 'vertical',
2361 + contents: [
2362 + {
2363 + type: 'text',
2364 + text: 'Body text',
2365 + },
2366 + ],
2367 + },
2368 + footer: {
2369 + type: 'box',
2370 + layout: 'vertical',
2371 + contents: [
2372 + {
2373 + type: 'text',
2374 + text: 'Footer text',
2375 + },
2376 + ],
2377 + },
2378 + styles: {
2379 + comment: 'See the example of a bubble style object',
2380 + },
2381 + },
2382 + },
2383 + ],
2384 + });
2385 + expect(config.headers).toEqual(headers);
2386 + return [200, reply];
2387 + });
2388 +
2389 + const res = await client.multicastFlex(
2390 + [RECIPIENT_ID],
2391 + 'this is a flex message',
2392 + {
2393 + type: 'bubble',
2394 + header: {
2395 + type: 'box',
2396 + layout: 'vertical',
2397 + contents: [
2398 + {
2399 + type: 'text',
2400 + text: 'Header text',
2401 + },
2402 + ],
2403 + },
2404 + hero: {
2405 + type: 'image',
2406 + url: 'https://example.com/flex/images/image.jpg',
2407 + },
2408 + body: {
2409 + type: 'box',
2410 + layout: 'vertical',
2411 + contents: [
2412 + {
2413 + type: 'text',
2414 + text: 'Body text',
2415 + },
2416 + ],
2417 + },
2418 + footer: {
2419 + type: 'box',
2420 + layout: 'vertical',
2421 + contents: [
2422 + {
2423 + type: 'text',
2424 + text: 'Footer text',
2425 + },
2426 + ],
2427 + },
2428 + styles: {
2429 + comment: 'See the example of a bubble style object',
2430 + },
2431 + },
2432 + { accessToken: CUSTOM_ACCESS_TOKEN }
2433 + );
2434 +
2435 + expect(res).toEqual(reply);
2436 + });
2437 + });
2438 +});
1 +import MockAdapter from 'axios-mock-adapter';
2 +
3 +import LineClient from '../LineClient';
4 +
5 +const RECIPIENT_ID = '1QAZ2WSX';
6 +const CUSTOM_ACCESS_TOKEN = '555555555';
7 +const ACCESS_TOKEN = '1234567890';
8 +const CHANNEL_SECRET = 'so-secret';
9 +
10 +const createMock = ({ customAccessToken } = {}) => {
11 + const client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
12 + const mock = new MockAdapter(client.axios);
13 + const headers = {
14 + Accept: 'application/json, text/plain, */*',
15 + 'Content-Type': 'application/json',
16 + Authorization: `Bearer ${customAccessToken || ACCESS_TOKEN}`,
17 + };
18 + return { client, mock, headers };
19 +};
20 +
21 +describe('Push Message', () => {
22 + describe('#pushRawBody', () => {
23 + it('should call push api', async () => {
24 + expect.assertions(4);
25 +
26 + const { client, mock, headers } = createMock();
27 +
28 + const reply = {};
29 +
30 + mock.onPost().reply(config => {
31 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
32 + expect(JSON.parse(config.data)).toEqual({
33 + to: RECIPIENT_ID,
34 + messages: [{ type: 'text', text: 'Hello!' }],
35 + });
36 + expect(config.headers).toEqual(headers);
37 + return [200, reply];
38 + });
39 +
40 + const res = await client.pushRawBody({
41 + to: RECIPIENT_ID,
42 + messages: [
43 + {
44 + type: 'text',
45 + text: 'Hello!',
46 + },
47 + ],
48 + });
49 +
50 + expect(res).toEqual(reply);
51 + });
52 +
53 + it('should work with custom access token', async () => {
54 + expect.assertions(4);
55 +
56 + const { client, mock, headers } = createMock({
57 + customAccessToken: CUSTOM_ACCESS_TOKEN,
58 + });
59 +
60 + const reply = {};
61 +
62 + mock.onPost().reply(config => {
63 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
64 + expect(JSON.parse(config.data)).toEqual({
65 + to: RECIPIENT_ID,
66 + messages: [{ type: 'text', text: 'Hello!' }],
67 + });
68 + expect(config.headers).toEqual(headers);
69 + return [200, reply];
70 + });
71 +
72 + const res = await client.pushRawBody(
73 + {
74 + to: RECIPIENT_ID,
75 + messages: [
76 + {
77 + type: 'text',
78 + text: 'Hello!',
79 + },
80 + ],
81 + },
82 + { accessToken: CUSTOM_ACCESS_TOKEN }
83 + );
84 +
85 + expect(res).toEqual(reply);
86 + });
87 + });
88 +
89 + describe('#push', () => {
90 + it('should call push api', async () => {
91 + expect.assertions(4);
92 +
93 + const { client, mock, headers } = createMock();
94 +
95 + const reply = {};
96 +
97 + mock.onPost().reply(config => {
98 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
99 + expect(JSON.parse(config.data)).toEqual({
100 + to: RECIPIENT_ID,
101 + messages: [{ type: 'text', text: 'Hello!' }],
102 + });
103 + expect(config.headers).toEqual(headers);
104 + return [200, reply];
105 + });
106 +
107 + const res = await client.push(RECIPIENT_ID, [
108 + {
109 + type: 'text',
110 + text: 'Hello!',
111 + },
112 + ]);
113 +
114 + expect(res).toEqual(reply);
115 + });
116 +
117 + it('should work with custom access token', async () => {
118 + expect.assertions(4);
119 +
120 + const { client, mock, headers } = createMock({
121 + customAccessToken: CUSTOM_ACCESS_TOKEN,
122 + });
123 +
124 + const reply = {};
125 +
126 + mock.onPost().reply(config => {
127 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
128 + expect(JSON.parse(config.data)).toEqual({
129 + to: RECIPIENT_ID,
130 + messages: [{ type: 'text', text: 'Hello!' }],
131 + });
132 + expect(config.headers).toEqual(headers);
133 + return [200, reply];
134 + });
135 +
136 + const res = await client.push(
137 + RECIPIENT_ID,
138 + [
139 + {
140 + type: 'text',
141 + text: 'Hello!',
142 + },
143 + ],
144 + { accessToken: CUSTOM_ACCESS_TOKEN }
145 + );
146 +
147 + expect(res).toEqual(reply);
148 + });
149 + });
150 +
151 + describe('#pushText', () => {
152 + it('should call push api', async () => {
153 + expect.assertions(4);
154 +
155 + const { client, mock, headers } = createMock();
156 +
157 + const reply = {};
158 +
159 + mock.onPost().reply(config => {
160 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
161 + expect(JSON.parse(config.data)).toEqual({
162 + to: RECIPIENT_ID,
163 + messages: [{ type: 'text', text: 'Hello!' }],
164 + });
165 + expect(config.headers).toEqual(headers);
166 + return [200, reply];
167 + });
168 +
169 + const res = await client.pushText(RECIPIENT_ID, 'Hello!');
170 +
171 + expect(res).toEqual(reply);
172 + });
173 +
174 + it('should work with custom access token', async () => {
175 + expect.assertions(4);
176 +
177 + const { client, mock, headers } = createMock({
178 + customAccessToken: CUSTOM_ACCESS_TOKEN,
179 + });
180 +
181 + const reply = {};
182 +
183 + mock.onPost().reply(config => {
184 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
185 + expect(JSON.parse(config.data)).toEqual({
186 + to: RECIPIENT_ID,
187 + messages: [{ type: 'text', text: 'Hello!' }],
188 + });
189 + expect(config.headers).toEqual(headers);
190 + return [200, reply];
191 + });
192 +
193 + const res = await client.pushText(RECIPIENT_ID, 'Hello!', {
194 + accessToken: CUSTOM_ACCESS_TOKEN,
195 + });
196 +
197 + expect(res).toEqual(reply);
198 + });
199 + });
200 +
201 + describe('#pushImage', () => {
202 + it('should call push api', async () => {
203 + expect.assertions(4);
204 +
205 + const { client, mock, headers } = createMock();
206 +
207 + const reply = {};
208 +
209 + mock.onPost().reply(config => {
210 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
211 + expect(JSON.parse(config.data)).toEqual({
212 + to: RECIPIENT_ID,
213 + messages: [
214 + {
215 + type: 'image',
216 + originalContentUrl: 'https://example.com/original.jpg',
217 + previewImageUrl: 'https://example.com/preview.jpg',
218 + },
219 + ],
220 + });
221 + expect(config.headers).toEqual(headers);
222 + return [200, reply];
223 + });
224 +
225 + const res = await client.pushImage(
226 + RECIPIENT_ID,
227 + 'https://example.com/original.jpg',
228 + 'https://example.com/preview.jpg'
229 + );
230 +
231 + expect(res).toEqual(reply);
232 + });
233 +
234 + it('should use contentUrl as fallback', async () => {
235 + expect.assertions(4);
236 +
237 + const { client, mock, headers } = createMock();
238 +
239 + const reply = {};
240 +
241 + mock.onPost().reply(config => {
242 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
243 + expect(JSON.parse(config.data)).toEqual({
244 + to: RECIPIENT_ID,
245 + messages: [
246 + {
247 + type: 'image',
248 + originalContentUrl: 'https://example.com/original.jpg',
249 + previewImageUrl: 'https://example.com/original.jpg',
250 + },
251 + ],
252 + });
253 + expect(config.headers).toEqual(headers);
254 + return [200, reply];
255 + });
256 +
257 + const res = await client.pushImage(
258 + RECIPIENT_ID,
259 + 'https://example.com/original.jpg'
260 + );
261 +
262 + expect(res).toEqual(reply);
263 + });
264 +
265 + it('should call push api with object image arg', async () => {
266 + expect.assertions(4);
267 +
268 + const { client, mock, headers } = createMock();
269 +
270 + const reply = {};
271 +
272 + mock.onPost().reply(config => {
273 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
274 + expect(JSON.parse(config.data)).toEqual({
275 + to: RECIPIENT_ID,
276 + messages: [
277 + {
278 + type: 'image',
279 + originalContentUrl: 'https://example.com/original.jpg',
280 + previewImageUrl: 'https://example.com/preview.jpg',
281 + },
282 + ],
283 + });
284 + expect(config.headers).toEqual(headers);
285 + return [200, reply];
286 + });
287 +
288 + const res = await client.pushImage(RECIPIENT_ID, {
289 + originalContentUrl: 'https://example.com/original.jpg',
290 + previewImageUrl: 'https://example.com/preview.jpg',
291 + });
292 +
293 + expect(res).toEqual(reply);
294 + });
295 +
296 + it('should work with custom access token', async () => {
297 + expect.assertions(4);
298 +
299 + const { client, mock, headers } = createMock({
300 + customAccessToken: CUSTOM_ACCESS_TOKEN,
301 + });
302 +
303 + const reply = {};
304 +
305 + mock.onPost().reply(config => {
306 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
307 + expect(JSON.parse(config.data)).toEqual({
308 + to: RECIPIENT_ID,
309 + messages: [
310 + {
311 + type: 'image',
312 + originalContentUrl: 'https://example.com/original.jpg',
313 + previewImageUrl: 'https://example.com/preview.jpg',
314 + },
315 + ],
316 + });
317 + expect(config.headers).toEqual(headers);
318 + return [200, reply];
319 + });
320 +
321 + const res = await client.pushImage(
322 + RECIPIENT_ID,
323 + {
324 + originalContentUrl: 'https://example.com/original.jpg',
325 + previewImageUrl: 'https://example.com/preview.jpg',
326 + },
327 + { accessToken: CUSTOM_ACCESS_TOKEN }
328 + );
329 +
330 + expect(res).toEqual(reply);
331 + });
332 + });
333 +
334 + describe('#pushVideo', () => {
335 + it('should call push api', async () => {
336 + expect.assertions(4);
337 +
338 + const { client, mock, headers } = createMock();
339 +
340 + const reply = {};
341 +
342 + mock.onPost().reply(config => {
343 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
344 + expect(JSON.parse(config.data)).toEqual({
345 + to: RECIPIENT_ID,
346 + messages: [
347 + {
348 + type: 'video',
349 + originalContentUrl: 'https://example.com/original.mp4',
350 + previewImageUrl: 'https://example.com/preview.jpg',
351 + },
352 + ],
353 + });
354 + expect(config.headers).toEqual(headers);
355 + return [200, reply];
356 + });
357 +
358 + const res = await client.pushVideo(
359 + RECIPIENT_ID,
360 + 'https://example.com/original.mp4',
361 + 'https://example.com/preview.jpg'
362 + );
363 +
364 + expect(res).toEqual(reply);
365 + });
366 +
367 + it('should call push api with object video arg', async () => {
368 + expect.assertions(4);
369 +
370 + const { client, mock, headers } = createMock();
371 +
372 + const reply = {};
373 +
374 + mock.onPost().reply(config => {
375 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
376 + expect(JSON.parse(config.data)).toEqual({
377 + to: RECIPIENT_ID,
378 + messages: [
379 + {
380 + type: 'video',
381 + originalContentUrl: 'https://example.com/original.mp4',
382 + previewImageUrl: 'https://example.com/preview.jpg',
383 + },
384 + ],
385 + });
386 + expect(config.headers).toEqual(headers);
387 + return [200, reply];
388 + });
389 +
390 + const res = await client.pushVideo(RECIPIENT_ID, {
391 + originalContentUrl: 'https://example.com/original.mp4',
392 + previewImageUrl: 'https://example.com/preview.jpg',
393 + });
394 +
395 + expect(res).toEqual(reply);
396 + });
397 +
398 + it('should work with custom access token', async () => {
399 + expect.assertions(4);
400 +
401 + const { client, mock, headers } = createMock({
402 + customAccessToken: CUSTOM_ACCESS_TOKEN,
403 + });
404 +
405 + const reply = {};
406 +
407 + mock.onPost().reply(config => {
408 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
409 + expect(JSON.parse(config.data)).toEqual({
410 + to: RECIPIENT_ID,
411 + messages: [
412 + {
413 + type: 'video',
414 + originalContentUrl: 'https://example.com/original.mp4',
415 + previewImageUrl: 'https://example.com/preview.jpg',
416 + },
417 + ],
418 + });
419 + expect(config.headers).toEqual(headers);
420 + return [200, reply];
421 + });
422 +
423 + const res = await client.pushVideo(
424 + RECIPIENT_ID,
425 + {
426 + originalContentUrl: 'https://example.com/original.mp4',
427 + previewImageUrl: 'https://example.com/preview.jpg',
428 + },
429 + { accessToken: CUSTOM_ACCESS_TOKEN }
430 + );
431 +
432 + expect(res).toEqual(reply);
433 + });
434 + });
435 +
436 + describe('#pushAudio', () => {
437 + it('should call push api', async () => {
438 + expect.assertions(4);
439 +
440 + const { client, mock, headers } = createMock();
441 +
442 + const reply = {};
443 +
444 + mock.onPost().reply(config => {
445 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
446 + expect(JSON.parse(config.data)).toEqual({
447 + to: RECIPIENT_ID,
448 + messages: [
449 + {
450 + type: 'audio',
451 + originalContentUrl: 'https://example.com/original.m4a',
452 + duration: 240000,
453 + },
454 + ],
455 + });
456 + expect(config.headers).toEqual(headers);
457 + return [200, reply];
458 + });
459 +
460 + const res = await client.pushAudio(
461 + RECIPIENT_ID,
462 + 'https://example.com/original.m4a',
463 + 240000
464 + );
465 +
466 + expect(res).toEqual(reply);
467 + });
468 +
469 + it('should call push api with object audio arg', async () => {
470 + expect.assertions(4);
471 +
472 + const { client, mock, headers } = createMock();
473 +
474 + const reply = {};
475 +
476 + mock.onPost().reply(config => {
477 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
478 + expect(JSON.parse(config.data)).toEqual({
479 + to: RECIPIENT_ID,
480 + messages: [
481 + {
482 + type: 'audio',
483 + originalContentUrl: 'https://example.com/original.m4a',
484 + duration: 240000,
485 + },
486 + ],
487 + });
488 + expect(config.headers).toEqual(headers);
489 + return [200, reply];
490 + });
491 +
492 + const res = await client.pushAudio(RECIPIENT_ID, {
493 + originalContentUrl: 'https://example.com/original.m4a',
494 + duration: 240000,
495 + });
496 +
497 + expect(res).toEqual(reply);
498 + });
499 +
500 + it('should work with custom access token', async () => {
501 + expect.assertions(4);
502 +
503 + const { client, mock, headers } = createMock({
504 + customAccessToken: CUSTOM_ACCESS_TOKEN,
505 + });
506 +
507 + const reply = {};
508 +
509 + mock.onPost().reply(config => {
510 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
511 + expect(JSON.parse(config.data)).toEqual({
512 + to: RECIPIENT_ID,
513 + messages: [
514 + {
515 + type: 'audio',
516 + originalContentUrl: 'https://example.com/original.m4a',
517 + duration: 240000,
518 + },
519 + ],
520 + });
521 + expect(config.headers).toEqual(headers);
522 + return [200, reply];
523 + });
524 +
525 + const res = await client.pushAudio(
526 + RECIPIENT_ID,
527 + {
528 + originalContentUrl: 'https://example.com/original.m4a',
529 + duration: 240000,
530 + },
531 + { accessToken: CUSTOM_ACCESS_TOKEN }
532 + );
533 +
534 + expect(res).toEqual(reply);
535 + });
536 + });
537 +
538 + describe('#pushLocation', () => {
539 + it('should call push api', async () => {
540 + expect.assertions(4);
541 +
542 + const { client, mock, headers } = createMock();
543 +
544 + const reply = {};
545 +
546 + mock.onPost().reply(config => {
547 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
548 + expect(JSON.parse(config.data)).toEqual({
549 + to: RECIPIENT_ID,
550 + messages: [
551 + {
552 + type: 'location',
553 + title: 'my location',
554 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
555 + latitude: 35.65910807942215,
556 + longitude: 139.70372892916203,
557 + },
558 + ],
559 + });
560 + expect(config.headers).toEqual(headers);
561 + return [200, reply];
562 + });
563 +
564 + const res = await client.pushLocation(RECIPIENT_ID, {
565 + title: 'my location',
566 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
567 + latitude: 35.65910807942215,
568 + longitude: 139.70372892916203,
569 + });
570 +
571 + expect(res).toEqual(reply);
572 + });
573 +
574 + it('should work with custom access token', async () => {
575 + expect.assertions(4);
576 +
577 + const { client, mock, headers } = createMock({
578 + customAccessToken: CUSTOM_ACCESS_TOKEN,
579 + });
580 +
581 + const reply = {};
582 +
583 + mock.onPost().reply(config => {
584 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
585 + expect(JSON.parse(config.data)).toEqual({
586 + to: RECIPIENT_ID,
587 + messages: [
588 + {
589 + type: 'location',
590 + title: 'my location',
591 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
592 + latitude: 35.65910807942215,
593 + longitude: 139.70372892916203,
594 + },
595 + ],
596 + });
597 + expect(config.headers).toEqual(headers);
598 + return [200, reply];
599 + });
600 +
601 + const res = await client.pushLocation(
602 + RECIPIENT_ID,
603 + {
604 + title: 'my location',
605 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
606 + latitude: 35.65910807942215,
607 + longitude: 139.70372892916203,
608 + },
609 + { accessToken: CUSTOM_ACCESS_TOKEN }
610 + );
611 +
612 + expect(res).toEqual(reply);
613 + });
614 + });
615 +
616 + describe('#pushSticker', () => {
617 + it('should call push api', async () => {
618 + expect.assertions(4);
619 +
620 + const { client, mock, headers } = createMock();
621 +
622 + const reply = {};
623 +
624 + mock.onPost().reply(config => {
625 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
626 + expect(JSON.parse(config.data)).toEqual({
627 + to: RECIPIENT_ID,
628 + messages: [
629 + {
630 + type: 'sticker',
631 + packageId: '1',
632 + stickerId: '1',
633 + },
634 + ],
635 + });
636 + expect(config.headers).toEqual(headers);
637 + return [200, reply];
638 + });
639 +
640 + const res = await client.pushSticker(RECIPIENT_ID, '1', '1');
641 +
642 + expect(res).toEqual(reply);
643 + });
644 +
645 + it('should call push api with object sticker arg', async () => {
646 + expect.assertions(4);
647 +
648 + const { client, mock, headers } = createMock();
649 +
650 + const reply = {};
651 +
652 + mock.onPost().reply(config => {
653 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
654 + expect(JSON.parse(config.data)).toEqual({
655 + to: RECIPIENT_ID,
656 + messages: [
657 + {
658 + type: 'sticker',
659 + packageId: '1',
660 + stickerId: '1',
661 + },
662 + ],
663 + });
664 + expect(config.headers).toEqual(headers);
665 + return [200, reply];
666 + });
667 +
668 + const res = await client.pushSticker(RECIPIENT_ID, {
669 + packageId: '1',
670 + stickerId: '1',
671 + });
672 +
673 + expect(res).toEqual(reply);
674 + });
675 +
676 + it('should work with custom access token', async () => {
677 + expect.assertions(4);
678 +
679 + const { client, mock, headers } = createMock({
680 + customAccessToken: CUSTOM_ACCESS_TOKEN,
681 + });
682 +
683 + const reply = {};
684 +
685 + mock.onPost().reply(config => {
686 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
687 + expect(JSON.parse(config.data)).toEqual({
688 + to: RECIPIENT_ID,
689 + messages: [
690 + {
691 + type: 'sticker',
692 + packageId: '1',
693 + stickerId: '1',
694 + },
695 + ],
696 + });
697 + expect(config.headers).toEqual(headers);
698 + return [200, reply];
699 + });
700 +
701 + const res = await client.pushSticker(
702 + RECIPIENT_ID,
703 + {
704 + packageId: '1',
705 + stickerId: '1',
706 + },
707 + { accessToken: CUSTOM_ACCESS_TOKEN }
708 + );
709 +
710 + expect(res).toEqual(reply);
711 + });
712 + });
713 +
714 + describe('#pushImagemap', () => {
715 + it('should call push api', async () => {
716 + expect.assertions(4);
717 +
718 + const { client, mock, headers } = createMock();
719 +
720 + const reply = {};
721 +
722 + mock.onPost().reply(config => {
723 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
724 + expect(JSON.parse(config.data)).toEqual({
725 + to: RECIPIENT_ID,
726 + messages: [
727 + {
728 + type: 'imagemap',
729 + baseUrl: 'https://example.com/bot/images/rm001',
730 + altText: 'this is an imagemap',
731 + baseSize: {
732 + height: 1040,
733 + width: 1040,
734 + },
735 + actions: [
736 + {
737 + type: 'uri',
738 + linkUri: 'https://example.com/',
739 + area: {
740 + x: 0,
741 + y: 0,
742 + width: 520,
743 + height: 1040,
744 + },
745 + },
746 + {
747 + type: 'message',
748 + text: 'hello',
749 + area: {
750 + x: 520,
751 + y: 0,
752 + width: 520,
753 + height: 1040,
754 + },
755 + },
756 + ],
757 + },
758 + ],
759 + });
760 + expect(config.headers).toEqual(headers);
761 + return [200, reply];
762 + });
763 +
764 + const res = await client.pushImagemap(
765 + RECIPIENT_ID,
766 + 'this is an imagemap',
767 + {
768 + baseUrl: 'https://example.com/bot/images/rm001',
769 + baseHeight: 1040,
770 + baseWidth: 1040,
771 + actions: [
772 + {
773 + type: 'uri',
774 + linkUri: 'https://example.com/',
775 + area: {
776 + x: 0,
777 + y: 0,
778 + width: 520,
779 + height: 1040,
780 + },
781 + },
782 + {
783 + type: 'message',
784 + text: 'hello',
785 + area: {
786 + x: 520,
787 + y: 0,
788 + width: 520,
789 + height: 1040,
790 + },
791 + },
792 + ],
793 + }
794 + );
795 +
796 + expect(res).toEqual(reply);
797 + });
798 +
799 + it('should work with custom access token', async () => {
800 + expect.assertions(4);
801 +
802 + const { client, mock, headers } = createMock({
803 + customAccessToken: CUSTOM_ACCESS_TOKEN,
804 + });
805 +
806 + const reply = {};
807 +
808 + mock.onPost().reply(config => {
809 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
810 + expect(JSON.parse(config.data)).toEqual({
811 + to: RECIPIENT_ID,
812 + messages: [
813 + {
814 + type: 'imagemap',
815 + baseUrl: 'https://example.com/bot/images/rm001',
816 + altText: 'this is an imagemap',
817 + baseSize: {
818 + height: 1040,
819 + width: 1040,
820 + },
821 + actions: [
822 + {
823 + type: 'uri',
824 + linkUri: 'https://example.com/',
825 + area: {
826 + x: 0,
827 + y: 0,
828 + width: 520,
829 + height: 1040,
830 + },
831 + },
832 + {
833 + type: 'message',
834 + text: 'hello',
835 + area: {
836 + x: 520,
837 + y: 0,
838 + width: 520,
839 + height: 1040,
840 + },
841 + },
842 + ],
843 + },
844 + ],
845 + });
846 + expect(config.headers).toEqual(headers);
847 + return [200, reply];
848 + });
849 +
850 + const res = await client.pushImagemap(
851 + RECIPIENT_ID,
852 + 'this is an imagemap',
853 + {
854 + baseUrl: 'https://example.com/bot/images/rm001',
855 + baseHeight: 1040,
856 + baseWidth: 1040,
857 + actions: [
858 + {
859 + type: 'uri',
860 + linkUri: 'https://example.com/',
861 + area: {
862 + x: 0,
863 + y: 0,
864 + width: 520,
865 + height: 1040,
866 + },
867 + },
868 + {
869 + type: 'message',
870 + text: 'hello',
871 + area: {
872 + x: 520,
873 + y: 0,
874 + width: 520,
875 + height: 1040,
876 + },
877 + },
878 + ],
879 + },
880 + { accessToken: CUSTOM_ACCESS_TOKEN }
881 + );
882 +
883 + expect(res).toEqual(reply);
884 + });
885 +
886 + it('should support baseSize argument', async () => {
887 + expect.assertions(4);
888 +
889 + const { client, mock, headers } = createMock();
890 +
891 + const reply = {};
892 +
893 + mock.onPost().reply(config => {
894 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
895 + expect(JSON.parse(config.data)).toEqual({
896 + to: RECIPIENT_ID,
897 + messages: [
898 + {
899 + type: 'imagemap',
900 + baseUrl: 'https://example.com/bot/images/rm001',
901 + altText: 'this is an imagemap',
902 + baseSize: {
903 + height: 1040,
904 + width: 1040,
905 + },
906 + actions: [
907 + {
908 + type: 'uri',
909 + linkUri: 'https://example.com/',
910 + area: {
911 + x: 0,
912 + y: 0,
913 + width: 520,
914 + height: 1040,
915 + },
916 + },
917 + {
918 + type: 'message',
919 + text: 'hello',
920 + area: {
921 + x: 520,
922 + y: 0,
923 + width: 520,
924 + height: 1040,
925 + },
926 + },
927 + ],
928 + },
929 + ],
930 + });
931 + expect(config.headers).toEqual(headers);
932 + return [200, reply];
933 + });
934 +
935 + const res = await client.pushImagemap(
936 + RECIPIENT_ID,
937 + 'this is an imagemap',
938 + {
939 + baseUrl: 'https://example.com/bot/images/rm001',
940 + baseSize: {
941 + height: 1040,
942 + width: 1040,
943 + },
944 + actions: [
945 + {
946 + type: 'uri',
947 + linkUri: 'https://example.com/',
948 + area: {
949 + x: 0,
950 + y: 0,
951 + width: 520,
952 + height: 1040,
953 + },
954 + },
955 + {
956 + type: 'message',
957 + text: 'hello',
958 + area: {
959 + x: 520,
960 + y: 0,
961 + width: 520,
962 + height: 1040,
963 + },
964 + },
965 + ],
966 + }
967 + );
968 +
969 + expect(res).toEqual(reply);
970 + });
971 +
972 + it('should support video', async () => {
973 + expect.assertions(4);
974 +
975 + const { client, mock, headers } = createMock();
976 +
977 + const reply = {};
978 +
979 + mock.onPost().reply(config => {
980 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
981 + expect(JSON.parse(config.data)).toEqual({
982 + to: RECIPIENT_ID,
983 + messages: [
984 + {
985 + type: 'imagemap',
986 + baseUrl: 'https://example.com/bot/images/rm001',
987 + altText: 'this is an imagemap',
988 + baseSize: {
989 + height: 1040,
990 + width: 1040,
991 + },
992 + video: {
993 + originalContentUrl: 'https://example.com/video.mp4',
994 + previewImageUrl: 'https://example.com/video_preview.jpg',
995 + area: {
996 + x: 0,
997 + y: 0,
998 + width: 1040,
999 + height: 585,
1000 + },
1001 + externalLink: {
1002 + linkUri: 'https://example.com/see_more.html',
1003 + label: 'See More',
1004 + },
1005 + },
1006 + actions: [
1007 + {
1008 + type: 'uri',
1009 + linkUri: 'https://example.com/',
1010 + area: {
1011 + x: 0,
1012 + y: 0,
1013 + width: 520,
1014 + height: 1040,
1015 + },
1016 + },
1017 + {
1018 + type: 'message',
1019 + text: 'hello',
1020 + area: {
1021 + x: 520,
1022 + y: 0,
1023 + width: 520,
1024 + height: 1040,
1025 + },
1026 + },
1027 + ],
1028 + },
1029 + ],
1030 + });
1031 + expect(config.headers).toEqual(headers);
1032 + return [200, reply];
1033 + });
1034 +
1035 + const res = await client.pushImagemap(
1036 + RECIPIENT_ID,
1037 + 'this is an imagemap',
1038 + {
1039 + baseUrl: 'https://example.com/bot/images/rm001',
1040 + baseSize: {
1041 + height: 1040,
1042 + width: 1040,
1043 + },
1044 + video: {
1045 + originalContentUrl: 'https://example.com/video.mp4',
1046 + previewImageUrl: 'https://example.com/video_preview.jpg',
1047 + area: {
1048 + x: 0,
1049 + y: 0,
1050 + width: 1040,
1051 + height: 585,
1052 + },
1053 + externalLink: {
1054 + linkUri: 'https://example.com/see_more.html',
1055 + label: 'See More',
1056 + },
1057 + },
1058 + actions: [
1059 + {
1060 + type: 'uri',
1061 + linkUri: 'https://example.com/',
1062 + area: {
1063 + x: 0,
1064 + y: 0,
1065 + width: 520,
1066 + height: 1040,
1067 + },
1068 + },
1069 + {
1070 + type: 'message',
1071 + text: 'hello',
1072 + area: {
1073 + x: 520,
1074 + y: 0,
1075 + width: 520,
1076 + height: 1040,
1077 + },
1078 + },
1079 + ],
1080 + }
1081 + );
1082 +
1083 + expect(res).toEqual(reply);
1084 + });
1085 + });
1086 +
1087 + describe('#pushTemplate', () => {
1088 + it('should call push api', async () => {
1089 + expect.assertions(4);
1090 +
1091 + const { client, mock, headers } = createMock();
1092 +
1093 + const reply = {};
1094 +
1095 + mock.onPost().reply(config => {
1096 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1097 + expect(JSON.parse(config.data)).toEqual({
1098 + to: RECIPIENT_ID,
1099 + messages: [
1100 + {
1101 + type: 'template',
1102 + altText: 'this is a template',
1103 + template: {
1104 + type: 'buttons',
1105 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1106 + title: 'Menu',
1107 + text: 'Please select',
1108 + actions: [
1109 + {
1110 + type: 'postback',
1111 + label: 'Buy',
1112 + data: 'action=buy&itemid=123',
1113 + },
1114 + {
1115 + type: 'postback',
1116 + label: 'Add to cart',
1117 + data: 'action=add&itemid=123',
1118 + },
1119 + {
1120 + type: 'uri',
1121 + label: 'View detail',
1122 + uri: 'http://example.com/page/123',
1123 + },
1124 + ],
1125 + },
1126 + },
1127 + ],
1128 + });
1129 + expect(config.headers).toEqual(headers);
1130 + return [200, reply];
1131 + });
1132 +
1133 + const res = await client.pushTemplate(
1134 + RECIPIENT_ID,
1135 + 'this is a template',
1136 + {
1137 + type: 'buttons',
1138 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1139 + title: 'Menu',
1140 + text: 'Please select',
1141 + actions: [
1142 + {
1143 + type: 'postback',
1144 + label: 'Buy',
1145 + data: 'action=buy&itemid=123',
1146 + },
1147 + {
1148 + type: 'postback',
1149 + label: 'Add to cart',
1150 + data: 'action=add&itemid=123',
1151 + },
1152 + {
1153 + type: 'uri',
1154 + label: 'View detail',
1155 + uri: 'http://example.com/page/123',
1156 + },
1157 + ],
1158 + }
1159 + );
1160 +
1161 + expect(res).toEqual(reply);
1162 + });
1163 +
1164 + it('should work with custom access token', async () => {
1165 + expect.assertions(4);
1166 +
1167 + const { client, mock, headers } = createMock({
1168 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1169 + });
1170 +
1171 + const reply = {};
1172 +
1173 + mock.onPost().reply(config => {
1174 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1175 + expect(JSON.parse(config.data)).toEqual({
1176 + to: RECIPIENT_ID,
1177 + messages: [
1178 + {
1179 + type: 'template',
1180 + altText: 'this is a template',
1181 + template: {
1182 + type: 'buttons',
1183 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1184 + title: 'Menu',
1185 + text: 'Please select',
1186 + actions: [
1187 + {
1188 + type: 'postback',
1189 + label: 'Buy',
1190 + data: 'action=buy&itemid=123',
1191 + },
1192 + {
1193 + type: 'postback',
1194 + label: 'Add to cart',
1195 + data: 'action=add&itemid=123',
1196 + },
1197 + {
1198 + type: 'uri',
1199 + label: 'View detail',
1200 + uri: 'http://example.com/page/123',
1201 + },
1202 + ],
1203 + },
1204 + },
1205 + ],
1206 + });
1207 + expect(config.headers).toEqual(headers);
1208 + return [200, reply];
1209 + });
1210 +
1211 + const res = await client.pushTemplate(
1212 + RECIPIENT_ID,
1213 + 'this is a template',
1214 + {
1215 + type: 'buttons',
1216 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1217 + title: 'Menu',
1218 + text: 'Please select',
1219 + actions: [
1220 + {
1221 + type: 'postback',
1222 + label: 'Buy',
1223 + data: 'action=buy&itemid=123',
1224 + },
1225 + {
1226 + type: 'postback',
1227 + label: 'Add to cart',
1228 + data: 'action=add&itemid=123',
1229 + },
1230 + {
1231 + type: 'uri',
1232 + label: 'View detail',
1233 + uri: 'http://example.com/page/123',
1234 + },
1235 + ],
1236 + },
1237 + { accessToken: CUSTOM_ACCESS_TOKEN }
1238 + );
1239 +
1240 + expect(res).toEqual(reply);
1241 + });
1242 + });
1243 +
1244 + describe('#pushButtonTemplate', () => {
1245 + it('should call push api', async () => {
1246 + expect.assertions(4);
1247 +
1248 + const { client, mock, headers } = createMock();
1249 +
1250 + const reply = {};
1251 +
1252 + mock.onPost().reply(config => {
1253 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1254 + expect(JSON.parse(config.data)).toEqual({
1255 + to: RECIPIENT_ID,
1256 + messages: [
1257 + {
1258 + type: 'template',
1259 + altText: 'this is a template',
1260 + template: {
1261 + type: 'buttons',
1262 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1263 + imageAspectRatio: 'rectangle',
1264 + imageSize: 'cover',
1265 + imageBackgroundColor: '#FFFFFF',
1266 + title: 'Menu',
1267 + text: 'Please select',
1268 + defaultAction: {
1269 + type: 'uri',
1270 + label: 'View detail',
1271 + uri: 'http://example.com/page/123',
1272 + },
1273 + actions: [
1274 + {
1275 + type: 'postback',
1276 + label: 'Buy',
1277 + data: 'action=buy&itemid=123',
1278 + },
1279 + {
1280 + type: 'postback',
1281 + label: 'Add to cart',
1282 + data: 'action=add&itemid=123',
1283 + },
1284 + {
1285 + type: 'uri',
1286 + label: 'View detail',
1287 + uri: 'http://example.com/page/123',
1288 + },
1289 + ],
1290 + },
1291 + },
1292 + ],
1293 + });
1294 + expect(config.headers).toEqual(headers);
1295 + return [200, reply];
1296 + });
1297 +
1298 + const res = await client.pushButtonTemplate(
1299 + RECIPIENT_ID,
1300 + 'this is a template',
1301 + {
1302 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1303 + imageAspectRatio: 'rectangle',
1304 + imageSize: 'cover',
1305 + imageBackgroundColor: '#FFFFFF',
1306 + title: 'Menu',
1307 + text: 'Please select',
1308 + defaultAction: {
1309 + type: 'uri',
1310 + label: 'View detail',
1311 + uri: 'http://example.com/page/123',
1312 + },
1313 + actions: [
1314 + {
1315 + type: 'postback',
1316 + label: 'Buy',
1317 + data: 'action=buy&itemid=123',
1318 + },
1319 + {
1320 + type: 'postback',
1321 + label: 'Add to cart',
1322 + data: 'action=add&itemid=123',
1323 + },
1324 + {
1325 + type: 'uri',
1326 + label: 'View detail',
1327 + uri: 'http://example.com/page/123',
1328 + },
1329 + ],
1330 + }
1331 + );
1332 +
1333 + expect(res).toEqual(reply);
1334 + });
1335 +
1336 + it('should work with custom access token', async () => {
1337 + expect.assertions(4);
1338 +
1339 + const { client, mock, headers } = createMock({
1340 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1341 + });
1342 +
1343 + const reply = {};
1344 +
1345 + mock.onPost().reply(config => {
1346 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1347 + expect(JSON.parse(config.data)).toEqual({
1348 + to: RECIPIENT_ID,
1349 + messages: [
1350 + {
1351 + type: 'template',
1352 + altText: 'this is a template',
1353 + template: {
1354 + type: 'buttons',
1355 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1356 + imageAspectRatio: 'rectangle',
1357 + imageSize: 'cover',
1358 + imageBackgroundColor: '#FFFFFF',
1359 + title: 'Menu',
1360 + text: 'Please select',
1361 + defaultAction: {
1362 + type: 'uri',
1363 + label: 'View detail',
1364 + uri: 'http://example.com/page/123',
1365 + },
1366 + actions: [
1367 + {
1368 + type: 'postback',
1369 + label: 'Buy',
1370 + data: 'action=buy&itemid=123',
1371 + },
1372 + {
1373 + type: 'postback',
1374 + label: 'Add to cart',
1375 + data: 'action=add&itemid=123',
1376 + },
1377 + {
1378 + type: 'uri',
1379 + label: 'View detail',
1380 + uri: 'http://example.com/page/123',
1381 + },
1382 + ],
1383 + },
1384 + },
1385 + ],
1386 + });
1387 + expect(config.headers).toEqual(headers);
1388 + return [200, reply];
1389 + });
1390 +
1391 + const res = await client.pushButtonTemplate(
1392 + RECIPIENT_ID,
1393 + 'this is a template',
1394 + {
1395 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1396 + imageAspectRatio: 'rectangle',
1397 + imageSize: 'cover',
1398 + imageBackgroundColor: '#FFFFFF',
1399 + title: 'Menu',
1400 + text: 'Please select',
1401 + defaultAction: {
1402 + type: 'uri',
1403 + label: 'View detail',
1404 + uri: 'http://example.com/page/123',
1405 + },
1406 + actions: [
1407 + {
1408 + type: 'postback',
1409 + label: 'Buy',
1410 + data: 'action=buy&itemid=123',
1411 + },
1412 + {
1413 + type: 'postback',
1414 + label: 'Add to cart',
1415 + data: 'action=add&itemid=123',
1416 + },
1417 + {
1418 + type: 'uri',
1419 + label: 'View detail',
1420 + uri: 'http://example.com/page/123',
1421 + },
1422 + ],
1423 + },
1424 + { accessToken: CUSTOM_ACCESS_TOKEN }
1425 + );
1426 +
1427 + expect(res).toEqual(reply);
1428 + });
1429 +
1430 + it('should support pushButtonsTemplate alias', async () => {
1431 + expect.assertions(4);
1432 +
1433 + const { client, mock, headers } = createMock();
1434 +
1435 + const reply = {};
1436 +
1437 + mock.onPost().reply(config => {
1438 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1439 + expect(JSON.parse(config.data)).toEqual({
1440 + to: RECIPIENT_ID,
1441 + messages: [
1442 + {
1443 + type: 'template',
1444 + altText: 'this is a template',
1445 + template: {
1446 + type: 'buttons',
1447 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1448 + imageAspectRatio: 'rectangle',
1449 + imageSize: 'cover',
1450 + imageBackgroundColor: '#FFFFFF',
1451 + title: 'Menu',
1452 + text: 'Please select',
1453 + actions: [
1454 + {
1455 + type: 'postback',
1456 + label: 'Buy',
1457 + data: 'action=buy&itemid=123',
1458 + },
1459 + {
1460 + type: 'postback',
1461 + label: 'Add to cart',
1462 + data: 'action=add&itemid=123',
1463 + },
1464 + {
1465 + type: 'uri',
1466 + label: 'View detail',
1467 + uri: 'http://example.com/page/123',
1468 + },
1469 + ],
1470 + },
1471 + },
1472 + ],
1473 + });
1474 + expect(config.headers).toEqual(headers);
1475 + return [200, reply];
1476 + });
1477 +
1478 + const res = await client.pushButtonsTemplate(
1479 + RECIPIENT_ID,
1480 + 'this is a template',
1481 + {
1482 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1483 + imageAspectRatio: 'rectangle',
1484 + imageSize: 'cover',
1485 + imageBackgroundColor: '#FFFFFF',
1486 + title: 'Menu',
1487 + text: 'Please select',
1488 + actions: [
1489 + {
1490 + type: 'postback',
1491 + label: 'Buy',
1492 + data: 'action=buy&itemid=123',
1493 + },
1494 + {
1495 + type: 'postback',
1496 + label: 'Add to cart',
1497 + data: 'action=add&itemid=123',
1498 + },
1499 + {
1500 + type: 'uri',
1501 + label: 'View detail',
1502 + uri: 'http://example.com/page/123',
1503 + },
1504 + ],
1505 + }
1506 + );
1507 +
1508 + expect(res).toEqual(reply);
1509 + });
1510 +
1511 + it('pushButtonsTemplate alias should work with custom access token', async () => {
1512 + expect.assertions(4);
1513 +
1514 + const { client, mock, headers } = createMock({
1515 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1516 + });
1517 +
1518 + const reply = {};
1519 +
1520 + mock.onPost().reply(config => {
1521 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1522 + expect(JSON.parse(config.data)).toEqual({
1523 + to: RECIPIENT_ID,
1524 + messages: [
1525 + {
1526 + type: 'template',
1527 + altText: 'this is a template',
1528 + template: {
1529 + type: 'buttons',
1530 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1531 + imageAspectRatio: 'rectangle',
1532 + imageSize: 'cover',
1533 + imageBackgroundColor: '#FFFFFF',
1534 + title: 'Menu',
1535 + text: 'Please select',
1536 + actions: [
1537 + {
1538 + type: 'postback',
1539 + label: 'Buy',
1540 + data: 'action=buy&itemid=123',
1541 + },
1542 + {
1543 + type: 'postback',
1544 + label: 'Add to cart',
1545 + data: 'action=add&itemid=123',
1546 + },
1547 + {
1548 + type: 'uri',
1549 + label: 'View detail',
1550 + uri: 'http://example.com/page/123',
1551 + },
1552 + ],
1553 + },
1554 + },
1555 + ],
1556 + });
1557 + expect(config.headers).toEqual(headers);
1558 + return [200, reply];
1559 + });
1560 +
1561 + const res = await client.pushButtonsTemplate(
1562 + RECIPIENT_ID,
1563 + 'this is a template',
1564 + {
1565 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1566 + imageAspectRatio: 'rectangle',
1567 + imageSize: 'cover',
1568 + imageBackgroundColor: '#FFFFFF',
1569 + title: 'Menu',
1570 + text: 'Please select',
1571 + actions: [
1572 + {
1573 + type: 'postback',
1574 + label: 'Buy',
1575 + data: 'action=buy&itemid=123',
1576 + },
1577 + {
1578 + type: 'postback',
1579 + label: 'Add to cart',
1580 + data: 'action=add&itemid=123',
1581 + },
1582 + {
1583 + type: 'uri',
1584 + label: 'View detail',
1585 + uri: 'http://example.com/page/123',
1586 + },
1587 + ],
1588 + },
1589 + { accessToken: CUSTOM_ACCESS_TOKEN }
1590 + );
1591 +
1592 + expect(res).toEqual(reply);
1593 + });
1594 + });
1595 +
1596 + describe('#pushConfirmTemplate', () => {
1597 + it('should call push api', async () => {
1598 + expect.assertions(4);
1599 +
1600 + const { client, mock, headers } = createMock();
1601 +
1602 + const reply = {};
1603 +
1604 + mock.onPost().reply(config => {
1605 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1606 + expect(JSON.parse(config.data)).toEqual({
1607 + to: RECIPIENT_ID,
1608 + messages: [
1609 + {
1610 + type: 'template',
1611 + altText: 'this is a confirm template',
1612 + template: {
1613 + type: 'confirm',
1614 + text: 'Are you sure?',
1615 + actions: [
1616 + {
1617 + type: 'message',
1618 + label: 'Yes',
1619 + text: 'yes',
1620 + },
1621 + {
1622 + type: 'message',
1623 + label: 'No',
1624 + text: 'no',
1625 + },
1626 + ],
1627 + },
1628 + },
1629 + ],
1630 + });
1631 + expect(config.headers).toEqual(headers);
1632 + return [200, reply];
1633 + });
1634 +
1635 + const res = await client.pushConfirmTemplate(
1636 + RECIPIENT_ID,
1637 + 'this is a confirm template',
1638 + {
1639 + text: 'Are you sure?',
1640 + actions: [
1641 + {
1642 + type: 'message',
1643 + label: 'Yes',
1644 + text: 'yes',
1645 + },
1646 + {
1647 + type: 'message',
1648 + label: 'No',
1649 + text: 'no',
1650 + },
1651 + ],
1652 + }
1653 + );
1654 +
1655 + expect(res).toEqual(reply);
1656 + });
1657 +
1658 + it('should work with custom access token', async () => {
1659 + expect.assertions(4);
1660 +
1661 + const { client, mock, headers } = createMock({
1662 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1663 + });
1664 +
1665 + const reply = {};
1666 +
1667 + mock.onPost().reply(config => {
1668 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1669 + expect(JSON.parse(config.data)).toEqual({
1670 + to: RECIPIENT_ID,
1671 + messages: [
1672 + {
1673 + type: 'template',
1674 + altText: 'this is a confirm template',
1675 + template: {
1676 + type: 'confirm',
1677 + text: 'Are you sure?',
1678 + actions: [
1679 + {
1680 + type: 'message',
1681 + label: 'Yes',
1682 + text: 'yes',
1683 + },
1684 + {
1685 + type: 'message',
1686 + label: 'No',
1687 + text: 'no',
1688 + },
1689 + ],
1690 + },
1691 + },
1692 + ],
1693 + });
1694 + expect(config.headers).toEqual(headers);
1695 + return [200, reply];
1696 + });
1697 +
1698 + const res = await client.pushConfirmTemplate(
1699 + RECIPIENT_ID,
1700 + 'this is a confirm template',
1701 + {
1702 + text: 'Are you sure?',
1703 + actions: [
1704 + {
1705 + type: 'message',
1706 + label: 'Yes',
1707 + text: 'yes',
1708 + },
1709 + {
1710 + type: 'message',
1711 + label: 'No',
1712 + text: 'no',
1713 + },
1714 + ],
1715 + },
1716 + { accessToken: CUSTOM_ACCESS_TOKEN }
1717 + );
1718 +
1719 + expect(res).toEqual(reply);
1720 + });
1721 + });
1722 +
1723 + describe('#pushCarouselTemplate', () => {
1724 + it('should call push api', async () => {
1725 + expect.assertions(4);
1726 +
1727 + const { client, mock, headers } = createMock();
1728 +
1729 + const reply = {};
1730 +
1731 + mock.onPost().reply(config => {
1732 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1733 + expect(JSON.parse(config.data)).toEqual({
1734 + to: RECIPIENT_ID,
1735 + messages: [
1736 + {
1737 + type: 'template',
1738 + altText: 'this is a carousel template',
1739 + template: {
1740 + type: 'carousel',
1741 + imageAspectRatio: 'rectangle',
1742 + imageSize: 'cover',
1743 + columns: [
1744 + {
1745 + thumbnailImageUrl:
1746 + 'https://example.com/bot/images/item1.jpg',
1747 + title: 'this is menu',
1748 + text: 'description',
1749 + actions: [
1750 + {
1751 + type: 'postback',
1752 + label: 'Buy',
1753 + data: 'action=buy&itemid=111',
1754 + },
1755 + {
1756 + type: 'postback',
1757 + label: 'Add to cart',
1758 + data: 'action=add&itemid=111',
1759 + },
1760 + {
1761 + type: 'uri',
1762 + label: 'View detail',
1763 + uri: 'http://example.com/page/111',
1764 + },
1765 + ],
1766 + },
1767 + {
1768 + thumbnailImageUrl:
1769 + 'https://example.com/bot/images/item2.jpg',
1770 + title: 'this is menu',
1771 + text: 'description',
1772 + actions: [
1773 + {
1774 + type: 'postback',
1775 + label: 'Buy',
1776 + data: 'action=buy&itemid=222',
1777 + },
1778 + {
1779 + type: 'postback',
1780 + label: 'Add to cart',
1781 + data: 'action=add&itemid=222',
1782 + },
1783 + {
1784 + type: 'uri',
1785 + label: 'View detail',
1786 + uri: 'http://example.com/page/222',
1787 + },
1788 + ],
1789 + },
1790 + ],
1791 + },
1792 + },
1793 + ],
1794 + });
1795 + expect(config.headers).toEqual(headers);
1796 + return [200, reply];
1797 + });
1798 +
1799 + const res = await client.pushCarouselTemplate(
1800 + RECIPIENT_ID,
1801 + 'this is a carousel template',
1802 + [
1803 + {
1804 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1805 + title: 'this is menu',
1806 + text: 'description',
1807 + actions: [
1808 + {
1809 + type: 'postback',
1810 + label: 'Buy',
1811 + data: 'action=buy&itemid=111',
1812 + },
1813 + {
1814 + type: 'postback',
1815 + label: 'Add to cart',
1816 + data: 'action=add&itemid=111',
1817 + },
1818 + {
1819 + type: 'uri',
1820 + label: 'View detail',
1821 + uri: 'http://example.com/page/111',
1822 + },
1823 + ],
1824 + },
1825 + {
1826 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1827 + title: 'this is menu',
1828 + text: 'description',
1829 + actions: [
1830 + {
1831 + type: 'postback',
1832 + label: 'Buy',
1833 + data: 'action=buy&itemid=222',
1834 + },
1835 + {
1836 + type: 'postback',
1837 + label: 'Add to cart',
1838 + data: 'action=add&itemid=222',
1839 + },
1840 + {
1841 + type: 'uri',
1842 + label: 'View detail',
1843 + uri: 'http://example.com/page/222',
1844 + },
1845 + ],
1846 + },
1847 + ],
1848 + {
1849 + imageAspectRatio: 'rectangle',
1850 + imageSize: 'cover',
1851 + }
1852 + );
1853 +
1854 + expect(res).toEqual(reply);
1855 + });
1856 +
1857 + it('should work with custom access token', async () => {
1858 + expect.assertions(4);
1859 +
1860 + const { client, mock, headers } = createMock({
1861 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1862 + });
1863 +
1864 + const reply = {};
1865 +
1866 + mock.onPost().reply(config => {
1867 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
1868 + expect(JSON.parse(config.data)).toEqual({
1869 + to: RECIPIENT_ID,
1870 + messages: [
1871 + {
1872 + type: 'template',
1873 + altText: 'this is a carousel template',
1874 + template: {
1875 + type: 'carousel',
1876 + imageAspectRatio: 'rectangle',
1877 + imageSize: 'cover',
1878 + columns: [
1879 + {
1880 + thumbnailImageUrl:
1881 + 'https://example.com/bot/images/item1.jpg',
1882 + title: 'this is menu',
1883 + text: 'description',
1884 + actions: [
1885 + {
1886 + type: 'postback',
1887 + label: 'Buy',
1888 + data: 'action=buy&itemid=111',
1889 + },
1890 + {
1891 + type: 'postback',
1892 + label: 'Add to cart',
1893 + data: 'action=add&itemid=111',
1894 + },
1895 + {
1896 + type: 'uri',
1897 + label: 'View detail',
1898 + uri: 'http://example.com/page/111',
1899 + },
1900 + ],
1901 + },
1902 + {
1903 + thumbnailImageUrl:
1904 + 'https://example.com/bot/images/item2.jpg',
1905 + title: 'this is menu',
1906 + text: 'description',
1907 + actions: [
1908 + {
1909 + type: 'postback',
1910 + label: 'Buy',
1911 + data: 'action=buy&itemid=222',
1912 + },
1913 + {
1914 + type: 'postback',
1915 + label: 'Add to cart',
1916 + data: 'action=add&itemid=222',
1917 + },
1918 + {
1919 + type: 'uri',
1920 + label: 'View detail',
1921 + uri: 'http://example.com/page/222',
1922 + },
1923 + ],
1924 + },
1925 + ],
1926 + },
1927 + },
1928 + ],
1929 + });
1930 + expect(config.headers).toEqual(headers);
1931 + return [200, reply];
1932 + });
1933 +
1934 + const res = await client.pushCarouselTemplate(
1935 + RECIPIENT_ID,
1936 + 'this is a carousel template',
1937 + [
1938 + {
1939 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1940 + title: 'this is menu',
1941 + text: 'description',
1942 + actions: [
1943 + {
1944 + type: 'postback',
1945 + label: 'Buy',
1946 + data: 'action=buy&itemid=111',
1947 + },
1948 + {
1949 + type: 'postback',
1950 + label: 'Add to cart',
1951 + data: 'action=add&itemid=111',
1952 + },
1953 + {
1954 + type: 'uri',
1955 + label: 'View detail',
1956 + uri: 'http://example.com/page/111',
1957 + },
1958 + ],
1959 + },
1960 + {
1961 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1962 + title: 'this is menu',
1963 + text: 'description',
1964 + actions: [
1965 + {
1966 + type: 'postback',
1967 + label: 'Buy',
1968 + data: 'action=buy&itemid=222',
1969 + },
1970 + {
1971 + type: 'postback',
1972 + label: 'Add to cart',
1973 + data: 'action=add&itemid=222',
1974 + },
1975 + {
1976 + type: 'uri',
1977 + label: 'View detail',
1978 + uri: 'http://example.com/page/222',
1979 + },
1980 + ],
1981 + },
1982 + ],
1983 + {
1984 + imageAspectRatio: 'rectangle',
1985 + imageSize: 'cover',
1986 + accessToken: CUSTOM_ACCESS_TOKEN,
1987 + }
1988 + );
1989 +
1990 + expect(res).toEqual(reply);
1991 + });
1992 +
1993 + it('should work without option', async () => {
1994 + expect.assertions(4);
1995 +
1996 + const { client, mock, headers } = createMock();
1997 +
1998 + const reply = {};
1999 +
2000 + mock.onPost().reply(config => {
2001 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
2002 + expect(JSON.parse(config.data)).toEqual({
2003 + to: RECIPIENT_ID,
2004 + messages: [
2005 + {
2006 + type: 'template',
2007 + altText: 'this is a carousel template',
2008 + template: {
2009 + type: 'carousel',
2010 + columns: [
2011 + {
2012 + thumbnailImageUrl:
2013 + 'https://example.com/bot/images/item1.jpg',
2014 + title: 'this is menu',
2015 + text: 'description',
2016 + actions: [
2017 + {
2018 + type: 'postback',
2019 + label: 'Buy',
2020 + data: 'action=buy&itemid=111',
2021 + },
2022 + {
2023 + type: 'postback',
2024 + label: 'Add to cart',
2025 + data: 'action=add&itemid=111',
2026 + },
2027 + {
2028 + type: 'uri',
2029 + label: 'View detail',
2030 + uri: 'http://example.com/page/111',
2031 + },
2032 + ],
2033 + },
2034 + {
2035 + thumbnailImageUrl:
2036 + 'https://example.com/bot/images/item2.jpg',
2037 + title: 'this is menu',
2038 + text: 'description',
2039 + actions: [
2040 + {
2041 + type: 'postback',
2042 + label: 'Buy',
2043 + data: 'action=buy&itemid=222',
2044 + },
2045 + {
2046 + type: 'postback',
2047 + label: 'Add to cart',
2048 + data: 'action=add&itemid=222',
2049 + },
2050 + {
2051 + type: 'uri',
2052 + label: 'View detail',
2053 + uri: 'http://example.com/page/222',
2054 + },
2055 + ],
2056 + },
2057 + ],
2058 + },
2059 + },
2060 + ],
2061 + });
2062 + expect(config.headers).toEqual(headers);
2063 + return [200, reply];
2064 + });
2065 +
2066 + const res = await client.pushCarouselTemplate(
2067 + RECIPIENT_ID,
2068 + 'this is a carousel template',
2069 + [
2070 + {
2071 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
2072 + title: 'this is menu',
2073 + text: 'description',
2074 + actions: [
2075 + {
2076 + type: 'postback',
2077 + label: 'Buy',
2078 + data: 'action=buy&itemid=111',
2079 + },
2080 + {
2081 + type: 'postback',
2082 + label: 'Add to cart',
2083 + data: 'action=add&itemid=111',
2084 + },
2085 + {
2086 + type: 'uri',
2087 + label: 'View detail',
2088 + uri: 'http://example.com/page/111',
2089 + },
2090 + ],
2091 + },
2092 + {
2093 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
2094 + title: 'this is menu',
2095 + text: 'description',
2096 + actions: [
2097 + {
2098 + type: 'postback',
2099 + label: 'Buy',
2100 + data: 'action=buy&itemid=222',
2101 + },
2102 + {
2103 + type: 'postback',
2104 + label: 'Add to cart',
2105 + data: 'action=add&itemid=222',
2106 + },
2107 + {
2108 + type: 'uri',
2109 + label: 'View detail',
2110 + uri: 'http://example.com/page/222',
2111 + },
2112 + ],
2113 + },
2114 + ]
2115 + );
2116 +
2117 + expect(res).toEqual(reply);
2118 + });
2119 + });
2120 +
2121 + describe('#pushImageCarouselTemplate', () => {
2122 + it('should call push api', async () => {
2123 + expect.assertions(4);
2124 +
2125 + const { client, mock, headers } = createMock();
2126 +
2127 + const reply = {};
2128 +
2129 + mock.onPost().reply(config => {
2130 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
2131 + expect(JSON.parse(config.data)).toEqual({
2132 + to: RECIPIENT_ID,
2133 + messages: [
2134 + {
2135 + type: 'template',
2136 + altText: 'this is an image carousel template',
2137 + template: {
2138 + type: 'image_carousel',
2139 + columns: [
2140 + {
2141 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2142 + action: {
2143 + type: 'postback',
2144 + label: 'Buy',
2145 + data: 'action=buy&itemid=111',
2146 + },
2147 + },
2148 + {
2149 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2150 + action: {
2151 + type: 'message',
2152 + label: 'Yes',
2153 + text: 'yes',
2154 + },
2155 + },
2156 + {
2157 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2158 + action: {
2159 + type: 'uri',
2160 + label: 'View detail',
2161 + uri: 'http://example.com/page/222',
2162 + },
2163 + },
2164 + ],
2165 + },
2166 + },
2167 + ],
2168 + });
2169 + expect(config.headers).toEqual(headers);
2170 + return [200, reply];
2171 + });
2172 +
2173 + const res = await client.pushImageCarouselTemplate(
2174 + RECIPIENT_ID,
2175 + 'this is an image carousel template',
2176 + [
2177 + {
2178 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2179 + action: {
2180 + type: 'postback',
2181 + label: 'Buy',
2182 + data: 'action=buy&itemid=111',
2183 + },
2184 + },
2185 + {
2186 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2187 + action: {
2188 + type: 'message',
2189 + label: 'Yes',
2190 + text: 'yes',
2191 + },
2192 + },
2193 + {
2194 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2195 + action: {
2196 + type: 'uri',
2197 + label: 'View detail',
2198 + uri: 'http://example.com/page/222',
2199 + },
2200 + },
2201 + ]
2202 + );
2203 +
2204 + expect(res).toEqual(reply);
2205 + });
2206 +
2207 + it('should work with custom access token', async () => {
2208 + expect.assertions(4);
2209 +
2210 + const { client, mock, headers } = createMock({
2211 + customAccessToken: CUSTOM_ACCESS_TOKEN,
2212 + });
2213 +
2214 + const reply = {};
2215 +
2216 + mock.onPost().reply(config => {
2217 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
2218 + expect(JSON.parse(config.data)).toEqual({
2219 + to: RECIPIENT_ID,
2220 + messages: [
2221 + {
2222 + type: 'template',
2223 + altText: 'this is an image carousel template',
2224 + template: {
2225 + type: 'image_carousel',
2226 + columns: [
2227 + {
2228 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2229 + action: {
2230 + type: 'postback',
2231 + label: 'Buy',
2232 + data: 'action=buy&itemid=111',
2233 + },
2234 + },
2235 + {
2236 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2237 + action: {
2238 + type: 'message',
2239 + label: 'Yes',
2240 + text: 'yes',
2241 + },
2242 + },
2243 + {
2244 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2245 + action: {
2246 + type: 'uri',
2247 + label: 'View detail',
2248 + uri: 'http://example.com/page/222',
2249 + },
2250 + },
2251 + ],
2252 + },
2253 + },
2254 + ],
2255 + });
2256 + expect(config.headers).toEqual(headers);
2257 + return [200, reply];
2258 + });
2259 +
2260 + const res = await client.pushImageCarouselTemplate(
2261 + RECIPIENT_ID,
2262 + 'this is an image carousel template',
2263 + [
2264 + {
2265 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2266 + action: {
2267 + type: 'postback',
2268 + label: 'Buy',
2269 + data: 'action=buy&itemid=111',
2270 + },
2271 + },
2272 + {
2273 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2274 + action: {
2275 + type: 'message',
2276 + label: 'Yes',
2277 + text: 'yes',
2278 + },
2279 + },
2280 + {
2281 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2282 + action: {
2283 + type: 'uri',
2284 + label: 'View detail',
2285 + uri: 'http://example.com/page/222',
2286 + },
2287 + },
2288 + ],
2289 + { accessToken: CUSTOM_ACCESS_TOKEN }
2290 + );
2291 +
2292 + expect(res).toEqual(reply);
2293 + });
2294 + });
2295 +
2296 + describe('#pushFlex', () => {
2297 + it('should call push api', async () => {
2298 + expect.assertions(4);
2299 +
2300 + const { client, mock, headers } = createMock();
2301 +
2302 + const reply = {};
2303 +
2304 + mock.onPost().reply(config => {
2305 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
2306 + expect(JSON.parse(config.data)).toEqual({
2307 + to: RECIPIENT_ID,
2308 + messages: [
2309 + {
2310 + type: 'flex',
2311 + altText: 'this is a flex message',
2312 + contents: {
2313 + type: 'bubble',
2314 + header: {
2315 + type: 'box',
2316 + layout: 'vertical',
2317 + contents: [
2318 + {
2319 + type: 'text',
2320 + text: 'Header text',
2321 + },
2322 + ],
2323 + },
2324 + hero: {
2325 + type: 'image',
2326 + url: 'https://example.com/flex/images/image.jpg',
2327 + },
2328 + body: {
2329 + type: 'box',
2330 + layout: 'vertical',
2331 + contents: [
2332 + {
2333 + type: 'text',
2334 + text: 'Body text',
2335 + },
2336 + ],
2337 + },
2338 + footer: {
2339 + type: 'box',
2340 + layout: 'vertical',
2341 + contents: [
2342 + {
2343 + type: 'text',
2344 + text: 'Footer text',
2345 + },
2346 + ],
2347 + },
2348 + styles: {
2349 + comment: 'See the example of a bubble style object',
2350 + },
2351 + },
2352 + },
2353 + ],
2354 + });
2355 + expect(config.headers).toEqual(headers);
2356 + return [200, reply];
2357 + });
2358 +
2359 + const res = await client.pushFlex(
2360 + RECIPIENT_ID,
2361 + 'this is a flex message',
2362 + {
2363 + type: 'bubble',
2364 + header: {
2365 + type: 'box',
2366 + layout: 'vertical',
2367 + contents: [
2368 + {
2369 + type: 'text',
2370 + text: 'Header text',
2371 + },
2372 + ],
2373 + },
2374 + hero: {
2375 + type: 'image',
2376 + url: 'https://example.com/flex/images/image.jpg',
2377 + },
2378 + body: {
2379 + type: 'box',
2380 + layout: 'vertical',
2381 + contents: [
2382 + {
2383 + type: 'text',
2384 + text: 'Body text',
2385 + },
2386 + ],
2387 + },
2388 + footer: {
2389 + type: 'box',
2390 + layout: 'vertical',
2391 + contents: [
2392 + {
2393 + type: 'text',
2394 + text: 'Footer text',
2395 + },
2396 + ],
2397 + },
2398 + styles: {
2399 + comment: 'See the example of a bubble style object',
2400 + },
2401 + }
2402 + );
2403 +
2404 + expect(res).toEqual(reply);
2405 + });
2406 +
2407 + it('should work with custom access token', async () => {
2408 + expect.assertions(4);
2409 +
2410 + const { client, mock, headers } = createMock({
2411 + customAccessToken: CUSTOM_ACCESS_TOKEN,
2412 + });
2413 +
2414 + const reply = {};
2415 +
2416 + mock.onPost().reply(config => {
2417 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/push');
2418 + expect(JSON.parse(config.data)).toEqual({
2419 + to: RECIPIENT_ID,
2420 + messages: [
2421 + {
2422 + type: 'flex',
2423 + altText: 'this is a flex message',
2424 + contents: {
2425 + type: 'bubble',
2426 + header: {
2427 + type: 'box',
2428 + layout: 'vertical',
2429 + contents: [
2430 + {
2431 + type: 'text',
2432 + text: 'Header text',
2433 + },
2434 + ],
2435 + },
2436 + hero: {
2437 + type: 'image',
2438 + url: 'https://example.com/flex/images/image.jpg',
2439 + },
2440 + body: {
2441 + type: 'box',
2442 + layout: 'vertical',
2443 + contents: [
2444 + {
2445 + type: 'text',
2446 + text: 'Body text',
2447 + },
2448 + ],
2449 + },
2450 + footer: {
2451 + type: 'box',
2452 + layout: 'vertical',
2453 + contents: [
2454 + {
2455 + type: 'text',
2456 + text: 'Footer text',
2457 + },
2458 + ],
2459 + },
2460 + styles: {
2461 + comment: 'See the example of a bubble style object',
2462 + },
2463 + },
2464 + },
2465 + ],
2466 + });
2467 + expect(config.headers).toEqual(headers);
2468 + return [200, reply];
2469 + });
2470 +
2471 + const res = await client.pushFlex(
2472 + RECIPIENT_ID,
2473 + 'this is a flex message',
2474 + {
2475 + type: 'bubble',
2476 + header: {
2477 + type: 'box',
2478 + layout: 'vertical',
2479 + contents: [
2480 + {
2481 + type: 'text',
2482 + text: 'Header text',
2483 + },
2484 + ],
2485 + },
2486 + hero: {
2487 + type: 'image',
2488 + url: 'https://example.com/flex/images/image.jpg',
2489 + },
2490 + body: {
2491 + type: 'box',
2492 + layout: 'vertical',
2493 + contents: [
2494 + {
2495 + type: 'text',
2496 + text: 'Body text',
2497 + },
2498 + ],
2499 + },
2500 + footer: {
2501 + type: 'box',
2502 + layout: 'vertical',
2503 + contents: [
2504 + {
2505 + type: 'text',
2506 + text: 'Footer text',
2507 + },
2508 + ],
2509 + },
2510 + styles: {
2511 + comment: 'See the example of a bubble style object',
2512 + },
2513 + },
2514 + { accessToken: CUSTOM_ACCESS_TOKEN }
2515 + );
2516 +
2517 + expect(res).toEqual(reply);
2518 + });
2519 + });
2520 +});
1 +import MockAdapter from 'axios-mock-adapter';
2 +
3 +import LineClient from '../LineClient';
4 +
5 +const REPLY_TOKEN = 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA';
6 +const CUSTOM_ACCESS_TOKEN = '555555555';
7 +const ACCESS_TOKEN = '1234567890';
8 +const CHANNEL_SECRET = 'so-secret';
9 +
10 +const createMock = ({ customAccessToken } = {}) => {
11 + const client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
12 + const mock = new MockAdapter(client.axios);
13 + const headers = {
14 + Accept: 'application/json, text/plain, */*',
15 + 'Content-Type': 'application/json',
16 + Authorization: `Bearer ${customAccessToken || ACCESS_TOKEN}`,
17 + };
18 + return { client, mock, headers };
19 +};
20 +
21 +describe('Reply Message', () => {
22 + describe('#replyRawBody', () => {
23 + it('should call reply api', async () => {
24 + expect.assertions(4);
25 +
26 + const { client, mock, headers } = createMock();
27 +
28 + const reply = {};
29 +
30 + mock.onPost().reply(config => {
31 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
32 + expect(JSON.parse(config.data)).toEqual({
33 + replyToken: REPLY_TOKEN,
34 + messages: [{ type: 'text', text: 'Hello!' }],
35 + });
36 + expect(config.headers).toEqual(headers);
37 + return [200, reply];
38 + });
39 +
40 + const res = await client.replyRawBody({
41 + replyToken: REPLY_TOKEN,
42 + messages: [
43 + {
44 + type: 'text',
45 + text: 'Hello!',
46 + },
47 + ],
48 + });
49 +
50 + expect(res).toEqual(reply);
51 + });
52 +
53 + it('should work with custom access token', async () => {
54 + expect.assertions(4);
55 +
56 + const { client, mock, headers } = createMock({
57 + customAccessToken: CUSTOM_ACCESS_TOKEN,
58 + });
59 +
60 + const reply = {};
61 +
62 + mock.onPost().reply(config => {
63 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
64 + expect(JSON.parse(config.data)).toEqual({
65 + replyToken: REPLY_TOKEN,
66 + messages: [{ type: 'text', text: 'Hello!' }],
67 + });
68 + expect(config.headers).toEqual(headers);
69 + return [200, reply];
70 + });
71 +
72 + const res = await client.replyRawBody(
73 + {
74 + replyToken: REPLY_TOKEN,
75 + messages: [
76 + {
77 + type: 'text',
78 + text: 'Hello!',
79 + },
80 + ],
81 + },
82 + { accessToken: CUSTOM_ACCESS_TOKEN }
83 + );
84 +
85 + expect(res).toEqual(reply);
86 + });
87 + });
88 +
89 + describe('#reply', () => {
90 + it('should call reply api', async () => {
91 + expect.assertions(4);
92 +
93 + const { client, mock, headers } = createMock();
94 +
95 + const reply = {};
96 +
97 + mock.onPost().reply(config => {
98 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
99 + expect(JSON.parse(config.data)).toEqual({
100 + replyToken: REPLY_TOKEN,
101 + messages: [{ type: 'text', text: 'Hello!' }],
102 + });
103 + expect(config.headers).toEqual(headers);
104 + return [200, reply];
105 + });
106 +
107 + const res = await client.reply(REPLY_TOKEN, [
108 + {
109 + type: 'text',
110 + text: 'Hello!',
111 + },
112 + ]);
113 +
114 + expect(res).toEqual(reply);
115 + });
116 +
117 + it('should work with custom access token', async () => {
118 + expect.assertions(4);
119 +
120 + const { client, mock, headers } = createMock({
121 + customAccessToken: CUSTOM_ACCESS_TOKEN,
122 + });
123 +
124 + const reply = {};
125 +
126 + mock.onPost().reply(config => {
127 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
128 + expect(JSON.parse(config.data)).toEqual({
129 + replyToken: REPLY_TOKEN,
130 + messages: [{ type: 'text', text: 'Hello!' }],
131 + });
132 + expect(config.headers).toEqual(headers);
133 + return [200, reply];
134 + });
135 +
136 + const res = await client.reply(
137 + REPLY_TOKEN,
138 + [
139 + {
140 + type: 'text',
141 + text: 'Hello!',
142 + },
143 + ],
144 + { accessToken: CUSTOM_ACCESS_TOKEN }
145 + );
146 +
147 + expect(res).toEqual(reply);
148 + });
149 + });
150 +
151 + describe('#replyText', () => {
152 + it('should call reply api', async () => {
153 + expect.assertions(4);
154 +
155 + const { client, mock, headers } = createMock();
156 +
157 + const reply = {};
158 +
159 + mock.onPost().reply(config => {
160 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
161 + expect(JSON.parse(config.data)).toEqual({
162 + replyToken: REPLY_TOKEN,
163 + messages: [{ type: 'text', text: 'Hello!' }],
164 + });
165 + expect(config.headers).toEqual(headers);
166 + return [200, reply];
167 + });
168 +
169 + const res = await client.replyText(REPLY_TOKEN, 'Hello!');
170 +
171 + expect(res).toEqual(reply);
172 + });
173 +
174 + it('should work with custom access token', async () => {
175 + expect.assertions(4);
176 +
177 + const { client, mock, headers } = createMock({
178 + customAccessToken: CUSTOM_ACCESS_TOKEN,
179 + });
180 +
181 + const reply = {};
182 +
183 + mock.onPost().reply(config => {
184 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
185 + expect(JSON.parse(config.data)).toEqual({
186 + replyToken: REPLY_TOKEN,
187 + messages: [{ type: 'text', text: 'Hello!' }],
188 + });
189 + expect(config.headers).toEqual(headers);
190 + return [200, reply];
191 + });
192 +
193 + const res = await client.replyText(REPLY_TOKEN, 'Hello!', {
194 + accessToken: CUSTOM_ACCESS_TOKEN,
195 + });
196 +
197 + expect(res).toEqual(reply);
198 + });
199 + });
200 +
201 + describe('#replyImage', () => {
202 + it('should call reply api', async () => {
203 + expect.assertions(4);
204 +
205 + const { client, mock, headers } = createMock();
206 +
207 + const reply = {};
208 +
209 + mock.onPost().reply(config => {
210 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
211 + expect(JSON.parse(config.data)).toEqual({
212 + replyToken: REPLY_TOKEN,
213 + messages: [
214 + {
215 + type: 'image',
216 + originalContentUrl: 'https://example.com/original.jpg',
217 + previewImageUrl: 'https://example.com/preview.jpg',
218 + },
219 + ],
220 + });
221 + expect(config.headers).toEqual(headers);
222 + return [200, reply];
223 + });
224 +
225 + const res = await client.replyImage(
226 + REPLY_TOKEN,
227 + 'https://example.com/original.jpg',
228 + 'https://example.com/preview.jpg'
229 + );
230 +
231 + expect(res).toEqual(reply);
232 + });
233 +
234 + it('should use contentUrl as fallback', async () => {
235 + expect.assertions(4);
236 +
237 + const { client, mock, headers } = createMock();
238 +
239 + const reply = {};
240 +
241 + mock.onPost().reply(config => {
242 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
243 + expect(JSON.parse(config.data)).toEqual({
244 + replyToken: REPLY_TOKEN,
245 + messages: [
246 + {
247 + type: 'image',
248 + originalContentUrl: 'https://example.com/original.jpg',
249 + previewImageUrl: 'https://example.com/original.jpg',
250 + },
251 + ],
252 + });
253 + expect(config.headers).toEqual(headers);
254 + return [200, reply];
255 + });
256 +
257 + const res = await client.replyImage(
258 + REPLY_TOKEN,
259 + 'https://example.com/original.jpg'
260 + );
261 +
262 + expect(res).toEqual(reply);
263 + });
264 +
265 + it('should call reply api with object image arg', async () => {
266 + expect.assertions(4);
267 +
268 + const { client, mock, headers } = createMock();
269 +
270 + const reply = {};
271 +
272 + mock.onPost().reply(config => {
273 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
274 + expect(JSON.parse(config.data)).toEqual({
275 + replyToken: REPLY_TOKEN,
276 + messages: [
277 + {
278 + type: 'image',
279 + originalContentUrl: 'https://example.com/original.jpg',
280 + previewImageUrl: 'https://example.com/preview.jpg',
281 + },
282 + ],
283 + });
284 + expect(config.headers).toEqual(headers);
285 + return [200, reply];
286 + });
287 +
288 + const res = await client.replyImage(REPLY_TOKEN, {
289 + originalContentUrl: 'https://example.com/original.jpg',
290 + previewImageUrl: 'https://example.com/preview.jpg',
291 + });
292 +
293 + expect(res).toEqual(reply);
294 + });
295 +
296 + it('should work with custom access token', async () => {
297 + expect.assertions(4);
298 +
299 + const { client, mock, headers } = createMock({
300 + customAccessToken: CUSTOM_ACCESS_TOKEN,
301 + });
302 +
303 + const reply = {};
304 +
305 + mock.onPost().reply(config => {
306 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
307 + expect(JSON.parse(config.data)).toEqual({
308 + replyToken: REPLY_TOKEN,
309 + messages: [
310 + {
311 + type: 'image',
312 + originalContentUrl: 'https://example.com/original.jpg',
313 + previewImageUrl: 'https://example.com/preview.jpg',
314 + },
315 + ],
316 + });
317 + expect(config.headers).toEqual(headers);
318 + return [200, reply];
319 + });
320 +
321 + const res = await client.replyImage(
322 + REPLY_TOKEN,
323 + {
324 + originalContentUrl: 'https://example.com/original.jpg',
325 + previewImageUrl: 'https://example.com/preview.jpg',
326 + },
327 + { accessToken: CUSTOM_ACCESS_TOKEN }
328 + );
329 +
330 + expect(res).toEqual(reply);
331 + });
332 + });
333 +
334 + describe('#replyVideo', () => {
335 + it('should call reply api', async () => {
336 + expect.assertions(4);
337 +
338 + const { client, mock, headers } = createMock();
339 +
340 + const reply = {};
341 +
342 + mock.onPost().reply(config => {
343 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
344 + expect(JSON.parse(config.data)).toEqual({
345 + replyToken: REPLY_TOKEN,
346 + messages: [
347 + {
348 + type: 'video',
349 + originalContentUrl: 'https://example.com/original.mp4',
350 + previewImageUrl: 'https://example.com/preview.jpg',
351 + },
352 + ],
353 + });
354 + expect(config.headers).toEqual(headers);
355 + return [200, reply];
356 + });
357 +
358 + const res = await client.replyVideo(
359 + REPLY_TOKEN,
360 + 'https://example.com/original.mp4',
361 + 'https://example.com/preview.jpg'
362 + );
363 +
364 + expect(res).toEqual(reply);
365 + });
366 +
367 + it('should call reply api with object video arg', async () => {
368 + expect.assertions(4);
369 +
370 + const { client, mock, headers } = createMock();
371 +
372 + const reply = {};
373 +
374 + mock.onPost().reply(config => {
375 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
376 + expect(JSON.parse(config.data)).toEqual({
377 + replyToken: REPLY_TOKEN,
378 + messages: [
379 + {
380 + type: 'video',
381 + originalContentUrl: 'https://example.com/original.mp4',
382 + previewImageUrl: 'https://example.com/preview.jpg',
383 + },
384 + ],
385 + });
386 + expect(config.headers).toEqual(headers);
387 + return [200, reply];
388 + });
389 +
390 + const res = await client.replyVideo(REPLY_TOKEN, {
391 + originalContentUrl: 'https://example.com/original.mp4',
392 + previewImageUrl: 'https://example.com/preview.jpg',
393 + });
394 +
395 + expect(res).toEqual(reply);
396 + });
397 +
398 + it('should work with custom access token', async () => {
399 + expect.assertions(4);
400 +
401 + const { client, mock, headers } = createMock({
402 + customAccessToken: CUSTOM_ACCESS_TOKEN,
403 + });
404 +
405 + const reply = {};
406 +
407 + mock.onPost().reply(config => {
408 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
409 + expect(JSON.parse(config.data)).toEqual({
410 + replyToken: REPLY_TOKEN,
411 + messages: [
412 + {
413 + type: 'video',
414 + originalContentUrl: 'https://example.com/original.mp4',
415 + previewImageUrl: 'https://example.com/preview.jpg',
416 + },
417 + ],
418 + });
419 + expect(config.headers).toEqual(headers);
420 + return [200, reply];
421 + });
422 +
423 + const res = await client.replyVideo(
424 + REPLY_TOKEN,
425 + {
426 + originalContentUrl: 'https://example.com/original.mp4',
427 + previewImageUrl: 'https://example.com/preview.jpg',
428 + },
429 + { accessToken: CUSTOM_ACCESS_TOKEN }
430 + );
431 +
432 + expect(res).toEqual(reply);
433 + });
434 + });
435 +
436 + describe('#replyAudio', () => {
437 + it('should call reply api', async () => {
438 + expect.assertions(4);
439 +
440 + const { client, mock, headers } = createMock();
441 +
442 + const reply = {};
443 +
444 + mock.onPost().reply(config => {
445 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
446 + expect(JSON.parse(config.data)).toEqual({
447 + replyToken: REPLY_TOKEN,
448 + messages: [
449 + {
450 + type: 'audio',
451 + originalContentUrl: 'https://example.com/original.m4a',
452 + duration: 240000,
453 + },
454 + ],
455 + });
456 + expect(config.headers).toEqual(headers);
457 + return [200, reply];
458 + });
459 +
460 + const res = await client.replyAudio(
461 + REPLY_TOKEN,
462 + 'https://example.com/original.m4a',
463 + 240000
464 + );
465 +
466 + expect(res).toEqual(reply);
467 + });
468 +
469 + it('should call reply api with object audio arg', async () => {
470 + expect.assertions(4);
471 +
472 + const { client, mock, headers } = createMock();
473 +
474 + const reply = {};
475 +
476 + mock.onPost().reply(config => {
477 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
478 + expect(JSON.parse(config.data)).toEqual({
479 + replyToken: REPLY_TOKEN,
480 + messages: [
481 + {
482 + type: 'audio',
483 + originalContentUrl: 'https://example.com/original.m4a',
484 + duration: 240000,
485 + },
486 + ],
487 + });
488 + expect(config.headers).toEqual(headers);
489 + return [200, reply];
490 + });
491 +
492 + const res = await client.replyAudio(REPLY_TOKEN, {
493 + originalContentUrl: 'https://example.com/original.m4a',
494 + duration: 240000,
495 + });
496 +
497 + expect(res).toEqual(reply);
498 + });
499 +
500 + it('should work with custom access token', async () => {
501 + expect.assertions(4);
502 +
503 + const { client, mock, headers } = createMock({
504 + customAccessToken: CUSTOM_ACCESS_TOKEN,
505 + });
506 +
507 + const reply = {};
508 +
509 + mock.onPost().reply(config => {
510 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
511 + expect(JSON.parse(config.data)).toEqual({
512 + replyToken: REPLY_TOKEN,
513 + messages: [
514 + {
515 + type: 'audio',
516 + originalContentUrl: 'https://example.com/original.m4a',
517 + duration: 240000,
518 + },
519 + ],
520 + });
521 + expect(config.headers).toEqual(headers);
522 + return [200, reply];
523 + });
524 +
525 + const res = await client.replyAudio(
526 + REPLY_TOKEN,
527 + {
528 + originalContentUrl: 'https://example.com/original.m4a',
529 + duration: 240000,
530 + },
531 + { accessToken: CUSTOM_ACCESS_TOKEN }
532 + );
533 +
534 + expect(res).toEqual(reply);
535 + });
536 + });
537 +
538 + describe('#replyLocation', () => {
539 + it('should call reply api', async () => {
540 + expect.assertions(4);
541 +
542 + const { client, mock, headers } = createMock();
543 +
544 + const reply = {};
545 +
546 + mock.onPost().reply(config => {
547 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
548 + expect(JSON.parse(config.data)).toEqual({
549 + replyToken: REPLY_TOKEN,
550 + messages: [
551 + {
552 + type: 'location',
553 + title: 'my location',
554 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
555 + latitude: 35.65910807942215,
556 + longitude: 139.70372892916203,
557 + },
558 + ],
559 + });
560 + expect(config.headers).toEqual(headers);
561 + return [200, reply];
562 + });
563 +
564 + const res = await client.replyLocation(REPLY_TOKEN, {
565 + title: 'my location',
566 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
567 + latitude: 35.65910807942215,
568 + longitude: 139.70372892916203,
569 + });
570 +
571 + expect(res).toEqual(reply);
572 + });
573 +
574 + it('should work with custom access token', async () => {
575 + expect.assertions(4);
576 +
577 + const { client, mock, headers } = createMock({
578 + customAccessToken: CUSTOM_ACCESS_TOKEN,
579 + });
580 +
581 + const reply = {};
582 +
583 + mock.onPost().reply(config => {
584 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
585 + expect(JSON.parse(config.data)).toEqual({
586 + replyToken: REPLY_TOKEN,
587 + messages: [
588 + {
589 + type: 'location',
590 + title: 'my location',
591 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
592 + latitude: 35.65910807942215,
593 + longitude: 139.70372892916203,
594 + },
595 + ],
596 + });
597 + expect(config.headers).toEqual(headers);
598 + return [200, reply];
599 + });
600 +
601 + const res = await client.replyLocation(
602 + REPLY_TOKEN,
603 + {
604 + title: 'my location',
605 + address: '〒150-0002 東京都渋谷区渋谷2丁目21−1',
606 + latitude: 35.65910807942215,
607 + longitude: 139.70372892916203,
608 + },
609 + { accessToken: CUSTOM_ACCESS_TOKEN }
610 + );
611 +
612 + expect(res).toEqual(reply);
613 + });
614 + });
615 +
616 + describe('#replySticker', () => {
617 + it('should call reply api', async () => {
618 + expect.assertions(4);
619 +
620 + const { client, mock, headers } = createMock();
621 +
622 + const reply = {};
623 +
624 + mock.onPost().reply(config => {
625 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
626 + expect(JSON.parse(config.data)).toEqual({
627 + replyToken: REPLY_TOKEN,
628 + messages: [
629 + {
630 + type: 'sticker',
631 + packageId: '1',
632 + stickerId: '1',
633 + },
634 + ],
635 + });
636 + expect(config.headers).toEqual(headers);
637 + return [200, reply];
638 + });
639 +
640 + const res = await client.replySticker(REPLY_TOKEN, '1', '1');
641 +
642 + expect(res).toEqual(reply);
643 + });
644 +
645 + it('should call reply api with object sticker arg', async () => {
646 + expect.assertions(4);
647 +
648 + const { client, mock, headers } = createMock();
649 +
650 + const reply = {};
651 +
652 + mock.onPost().reply(config => {
653 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
654 + expect(JSON.parse(config.data)).toEqual({
655 + replyToken: REPLY_TOKEN,
656 + messages: [
657 + {
658 + type: 'sticker',
659 + packageId: '1',
660 + stickerId: '1',
661 + },
662 + ],
663 + });
664 + expect(config.headers).toEqual(headers);
665 + return [200, reply];
666 + });
667 +
668 + const res = await client.replySticker(REPLY_TOKEN, {
669 + packageId: '1',
670 + stickerId: '1',
671 + });
672 +
673 + expect(res).toEqual(reply);
674 + });
675 +
676 + it('should work with custom access token', async () => {
677 + expect.assertions(4);
678 +
679 + const { client, mock, headers } = createMock({
680 + customAccessToken: CUSTOM_ACCESS_TOKEN,
681 + });
682 +
683 + const reply = {};
684 +
685 + mock.onPost().reply(config => {
686 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
687 + expect(JSON.parse(config.data)).toEqual({
688 + replyToken: REPLY_TOKEN,
689 + messages: [
690 + {
691 + type: 'sticker',
692 + packageId: '1',
693 + stickerId: '1',
694 + },
695 + ],
696 + });
697 + expect(config.headers).toEqual(headers);
698 + return [200, reply];
699 + });
700 +
701 + const res = await client.replySticker(
702 + REPLY_TOKEN,
703 + {
704 + packageId: '1',
705 + stickerId: '1',
706 + },
707 + { accessToken: CUSTOM_ACCESS_TOKEN }
708 + );
709 +
710 + expect(res).toEqual(reply);
711 + });
712 + });
713 +
714 + describe('#replyImagemap', () => {
715 + it('should call reply api', async () => {
716 + expect.assertions(4);
717 +
718 + const { client, mock, headers } = createMock();
719 +
720 + const reply = {};
721 +
722 + mock.onPost().reply(config => {
723 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
724 + expect(JSON.parse(config.data)).toEqual({
725 + replyToken: REPLY_TOKEN,
726 + messages: [
727 + {
728 + type: 'imagemap',
729 + baseUrl: 'https://example.com/bot/images/rm001',
730 + altText: 'this is an imagemap',
731 + baseSize: {
732 + height: 1040,
733 + width: 1040,
734 + },
735 + actions: [
736 + {
737 + type: 'uri',
738 + linkUri: 'https://example.com/',
739 + area: {
740 + x: 0,
741 + y: 0,
742 + width: 520,
743 + height: 1040,
744 + },
745 + },
746 + {
747 + type: 'message',
748 + text: 'hello',
749 + area: {
750 + x: 520,
751 + y: 0,
752 + width: 520,
753 + height: 1040,
754 + },
755 + },
756 + ],
757 + },
758 + ],
759 + });
760 + expect(config.headers).toEqual(headers);
761 + return [200, reply];
762 + });
763 +
764 + const res = await client.replyImagemap(
765 + REPLY_TOKEN,
766 + 'this is an imagemap',
767 + {
768 + baseUrl: 'https://example.com/bot/images/rm001',
769 + baseHeight: 1040,
770 + baseWidth: 1040,
771 + actions: [
772 + {
773 + type: 'uri',
774 + linkUri: 'https://example.com/',
775 + area: {
776 + x: 0,
777 + y: 0,
778 + width: 520,
779 + height: 1040,
780 + },
781 + },
782 + {
783 + type: 'message',
784 + text: 'hello',
785 + area: {
786 + x: 520,
787 + y: 0,
788 + width: 520,
789 + height: 1040,
790 + },
791 + },
792 + ],
793 + }
794 + );
795 +
796 + expect(res).toEqual(reply);
797 + });
798 +
799 + it('should work with custom access token', async () => {
800 + expect.assertions(4);
801 +
802 + const { client, mock, headers } = createMock({
803 + customAccessToken: CUSTOM_ACCESS_TOKEN,
804 + });
805 +
806 + const reply = {};
807 +
808 + mock.onPost().reply(config => {
809 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
810 + expect(JSON.parse(config.data)).toEqual({
811 + replyToken: REPLY_TOKEN,
812 + messages: [
813 + {
814 + type: 'imagemap',
815 + baseUrl: 'https://example.com/bot/images/rm001',
816 + altText: 'this is an imagemap',
817 + baseSize: {
818 + height: 1040,
819 + width: 1040,
820 + },
821 + actions: [
822 + {
823 + type: 'uri',
824 + linkUri: 'https://example.com/',
825 + area: {
826 + x: 0,
827 + y: 0,
828 + width: 520,
829 + height: 1040,
830 + },
831 + },
832 + {
833 + type: 'message',
834 + text: 'hello',
835 + area: {
836 + x: 520,
837 + y: 0,
838 + width: 520,
839 + height: 1040,
840 + },
841 + },
842 + ],
843 + },
844 + ],
845 + });
846 + expect(config.headers).toEqual(headers);
847 + return [200, reply];
848 + });
849 +
850 + const res = await client.replyImagemap(
851 + REPLY_TOKEN,
852 + 'this is an imagemap',
853 + {
854 + baseUrl: 'https://example.com/bot/images/rm001',
855 + baseHeight: 1040,
856 + baseWidth: 1040,
857 + actions: [
858 + {
859 + type: 'uri',
860 + linkUri: 'https://example.com/',
861 + area: {
862 + x: 0,
863 + y: 0,
864 + width: 520,
865 + height: 1040,
866 + },
867 + },
868 + {
869 + type: 'message',
870 + text: 'hello',
871 + area: {
872 + x: 520,
873 + y: 0,
874 + width: 520,
875 + height: 1040,
876 + },
877 + },
878 + ],
879 + },
880 + { accessToken: CUSTOM_ACCESS_TOKEN }
881 + );
882 +
883 + expect(res).toEqual(reply);
884 + });
885 +
886 + it('should support baseSize argument', async () => {
887 + expect.assertions(4);
888 +
889 + const { client, mock, headers } = createMock();
890 +
891 + const reply = {};
892 +
893 + mock.onPost().reply(config => {
894 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
895 + expect(JSON.parse(config.data)).toEqual({
896 + replyToken: REPLY_TOKEN,
897 + messages: [
898 + {
899 + type: 'imagemap',
900 + baseUrl: 'https://example.com/bot/images/rm001',
901 + altText: 'this is an imagemap',
902 + baseSize: {
903 + height: 1040,
904 + width: 1040,
905 + },
906 + actions: [
907 + {
908 + type: 'uri',
909 + linkUri: 'https://example.com/',
910 + area: {
911 + x: 0,
912 + y: 0,
913 + width: 520,
914 + height: 1040,
915 + },
916 + },
917 + {
918 + type: 'message',
919 + text: 'hello',
920 + area: {
921 + x: 520,
922 + y: 0,
923 + width: 520,
924 + height: 1040,
925 + },
926 + },
927 + ],
928 + },
929 + ],
930 + });
931 + expect(config.headers).toEqual(headers);
932 + return [200, reply];
933 + });
934 +
935 + const res = await client.replyImagemap(
936 + REPLY_TOKEN,
937 + 'this is an imagemap',
938 + {
939 + baseUrl: 'https://example.com/bot/images/rm001',
940 + baseSize: {
941 + height: 1040,
942 + width: 1040,
943 + },
944 + actions: [
945 + {
946 + type: 'uri',
947 + linkUri: 'https://example.com/',
948 + area: {
949 + x: 0,
950 + y: 0,
951 + width: 520,
952 + height: 1040,
953 + },
954 + },
955 + {
956 + type: 'message',
957 + text: 'hello',
958 + area: {
959 + x: 520,
960 + y: 0,
961 + width: 520,
962 + height: 1040,
963 + },
964 + },
965 + ],
966 + }
967 + );
968 +
969 + expect(res).toEqual(reply);
970 + });
971 +
972 + it('should support video', async () => {
973 + expect.assertions(4);
974 +
975 + const { client, mock, headers } = createMock();
976 +
977 + const reply = {};
978 +
979 + mock.onPost().reply(config => {
980 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
981 + expect(JSON.parse(config.data)).toEqual({
982 + replyToken: REPLY_TOKEN,
983 + messages: [
984 + {
985 + type: 'imagemap',
986 + baseUrl: 'https://example.com/bot/images/rm001',
987 + altText: 'this is an imagemap',
988 + baseSize: {
989 + height: 1040,
990 + width: 1040,
991 + },
992 + video: {
993 + originalContentUrl: 'https://example.com/video.mp4',
994 + previewImageUrl: 'https://example.com/video_preview.jpg',
995 + area: {
996 + x: 0,
997 + y: 0,
998 + width: 1040,
999 + height: 585,
1000 + },
1001 + externalLink: {
1002 + linkUri: 'https://example.com/see_more.html',
1003 + label: 'See More',
1004 + },
1005 + },
1006 + actions: [
1007 + {
1008 + type: 'uri',
1009 + linkUri: 'https://example.com/',
1010 + area: {
1011 + x: 0,
1012 + y: 0,
1013 + width: 520,
1014 + height: 1040,
1015 + },
1016 + },
1017 + {
1018 + type: 'message',
1019 + text: 'hello',
1020 + area: {
1021 + x: 520,
1022 + y: 0,
1023 + width: 520,
1024 + height: 1040,
1025 + },
1026 + },
1027 + ],
1028 + },
1029 + ],
1030 + });
1031 + expect(config.headers).toEqual(headers);
1032 + return [200, reply];
1033 + });
1034 +
1035 + const res = await client.replyImagemap(
1036 + REPLY_TOKEN,
1037 + 'this is an imagemap',
1038 + {
1039 + baseUrl: 'https://example.com/bot/images/rm001',
1040 + baseSize: {
1041 + height: 1040,
1042 + width: 1040,
1043 + },
1044 + video: {
1045 + originalContentUrl: 'https://example.com/video.mp4',
1046 + previewImageUrl: 'https://example.com/video_preview.jpg',
1047 + area: {
1048 + x: 0,
1049 + y: 0,
1050 + width: 1040,
1051 + height: 585,
1052 + },
1053 + externalLink: {
1054 + linkUri: 'https://example.com/see_more.html',
1055 + label: 'See More',
1056 + },
1057 + },
1058 + actions: [
1059 + {
1060 + type: 'uri',
1061 + linkUri: 'https://example.com/',
1062 + area: {
1063 + x: 0,
1064 + y: 0,
1065 + width: 520,
1066 + height: 1040,
1067 + },
1068 + },
1069 + {
1070 + type: 'message',
1071 + text: 'hello',
1072 + area: {
1073 + x: 520,
1074 + y: 0,
1075 + width: 520,
1076 + height: 1040,
1077 + },
1078 + },
1079 + ],
1080 + }
1081 + );
1082 +
1083 + expect(res).toEqual(reply);
1084 + });
1085 + });
1086 +
1087 + describe('#replyTemplate', () => {
1088 + it('should call reply api', async () => {
1089 + expect.assertions(4);
1090 +
1091 + const { client, mock, headers } = createMock();
1092 +
1093 + const reply = {};
1094 +
1095 + mock.onPost().reply(config => {
1096 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1097 + expect(JSON.parse(config.data)).toEqual({
1098 + replyToken: REPLY_TOKEN,
1099 + messages: [
1100 + {
1101 + type: 'template',
1102 + altText: 'this is a template',
1103 + template: {
1104 + type: 'buttons',
1105 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1106 + title: 'Menu',
1107 + text: 'Please select',
1108 + actions: [
1109 + {
1110 + type: 'postback',
1111 + label: 'Buy',
1112 + data: 'action=buy&itemid=123',
1113 + },
1114 + {
1115 + type: 'postback',
1116 + label: 'Add to cart',
1117 + data: 'action=add&itemid=123',
1118 + },
1119 + {
1120 + type: 'uri',
1121 + label: 'View detail',
1122 + uri: 'http://example.com/page/123',
1123 + },
1124 + ],
1125 + },
1126 + },
1127 + ],
1128 + });
1129 + expect(config.headers).toEqual(headers);
1130 + return [200, reply];
1131 + });
1132 +
1133 + const res = await client.replyTemplate(
1134 + REPLY_TOKEN,
1135 + 'this is a template',
1136 + {
1137 + type: 'buttons',
1138 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1139 + title: 'Menu',
1140 + text: 'Please select',
1141 + actions: [
1142 + {
1143 + type: 'postback',
1144 + label: 'Buy',
1145 + data: 'action=buy&itemid=123',
1146 + },
1147 + {
1148 + type: 'postback',
1149 + label: 'Add to cart',
1150 + data: 'action=add&itemid=123',
1151 + },
1152 + {
1153 + type: 'uri',
1154 + label: 'View detail',
1155 + uri: 'http://example.com/page/123',
1156 + },
1157 + ],
1158 + }
1159 + );
1160 +
1161 + expect(res).toEqual(reply);
1162 + });
1163 +
1164 + it('should work with custom access token', async () => {
1165 + expect.assertions(4);
1166 +
1167 + const { client, mock, headers } = createMock({
1168 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1169 + });
1170 +
1171 + const reply = {};
1172 +
1173 + mock.onPost().reply(config => {
1174 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1175 + expect(JSON.parse(config.data)).toEqual({
1176 + replyToken: REPLY_TOKEN,
1177 + messages: [
1178 + {
1179 + type: 'template',
1180 + altText: 'this is a template',
1181 + template: {
1182 + type: 'buttons',
1183 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1184 + title: 'Menu',
1185 + text: 'Please select',
1186 + actions: [
1187 + {
1188 + type: 'postback',
1189 + label: 'Buy',
1190 + data: 'action=buy&itemid=123',
1191 + },
1192 + {
1193 + type: 'postback',
1194 + label: 'Add to cart',
1195 + data: 'action=add&itemid=123',
1196 + },
1197 + {
1198 + type: 'uri',
1199 + label: 'View detail',
1200 + uri: 'http://example.com/page/123',
1201 + },
1202 + ],
1203 + },
1204 + },
1205 + ],
1206 + });
1207 + expect(config.headers).toEqual(headers);
1208 + return [200, reply];
1209 + });
1210 +
1211 + const res = await client.replyTemplate(
1212 + REPLY_TOKEN,
1213 + 'this is a template',
1214 + {
1215 + type: 'buttons',
1216 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1217 + title: 'Menu',
1218 + text: 'Please select',
1219 + actions: [
1220 + {
1221 + type: 'postback',
1222 + label: 'Buy',
1223 + data: 'action=buy&itemid=123',
1224 + },
1225 + {
1226 + type: 'postback',
1227 + label: 'Add to cart',
1228 + data: 'action=add&itemid=123',
1229 + },
1230 + {
1231 + type: 'uri',
1232 + label: 'View detail',
1233 + uri: 'http://example.com/page/123',
1234 + },
1235 + ],
1236 + },
1237 + { accessToken: CUSTOM_ACCESS_TOKEN }
1238 + );
1239 +
1240 + expect(res).toEqual(reply);
1241 + });
1242 + });
1243 +
1244 + describe('#replyButtonTemplate', () => {
1245 + it('should call reply api', async () => {
1246 + expect.assertions(4);
1247 +
1248 + const { client, mock, headers } = createMock();
1249 +
1250 + const reply = {};
1251 +
1252 + mock.onPost().reply(config => {
1253 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1254 + expect(JSON.parse(config.data)).toEqual({
1255 + replyToken: REPLY_TOKEN,
1256 + messages: [
1257 + {
1258 + type: 'template',
1259 + altText: 'this is a template',
1260 + template: {
1261 + type: 'buttons',
1262 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1263 + imageAspectRatio: 'rectangle',
1264 + imageSize: 'cover',
1265 + imageBackgroundColor: '#FFFFFF',
1266 + title: 'Menu',
1267 + text: 'Please select',
1268 + defaultAction: {
1269 + type: 'uri',
1270 + label: 'View detail',
1271 + uri: 'http://example.com/page/123',
1272 + },
1273 + actions: [
1274 + {
1275 + type: 'postback',
1276 + label: 'Buy',
1277 + data: 'action=buy&itemid=123',
1278 + },
1279 + {
1280 + type: 'postback',
1281 + label: 'Add to cart',
1282 + data: 'action=add&itemid=123',
1283 + },
1284 + {
1285 + type: 'uri',
1286 + label: 'View detail',
1287 + uri: 'http://example.com/page/123',
1288 + },
1289 + ],
1290 + },
1291 + },
1292 + ],
1293 + });
1294 + expect(config.headers).toEqual(headers);
1295 + return [200, reply];
1296 + });
1297 +
1298 + const res = await client.replyButtonTemplate(
1299 + REPLY_TOKEN,
1300 + 'this is a template',
1301 + {
1302 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1303 + imageAspectRatio: 'rectangle',
1304 + imageSize: 'cover',
1305 + imageBackgroundColor: '#FFFFFF',
1306 + title: 'Menu',
1307 + text: 'Please select',
1308 + defaultAction: {
1309 + type: 'uri',
1310 + label: 'View detail',
1311 + uri: 'http://example.com/page/123',
1312 + },
1313 + actions: [
1314 + {
1315 + type: 'postback',
1316 + label: 'Buy',
1317 + data: 'action=buy&itemid=123',
1318 + },
1319 + {
1320 + type: 'postback',
1321 + label: 'Add to cart',
1322 + data: 'action=add&itemid=123',
1323 + },
1324 + {
1325 + type: 'uri',
1326 + label: 'View detail',
1327 + uri: 'http://example.com/page/123',
1328 + },
1329 + ],
1330 + }
1331 + );
1332 +
1333 + expect(res).toEqual(reply);
1334 + });
1335 +
1336 + it('should work with custom access token', async () => {
1337 + expect.assertions(4);
1338 +
1339 + const { client, mock, headers } = createMock({
1340 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1341 + });
1342 +
1343 + const reply = {};
1344 +
1345 + mock.onPost().reply(config => {
1346 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1347 + expect(JSON.parse(config.data)).toEqual({
1348 + replyToken: REPLY_TOKEN,
1349 + messages: [
1350 + {
1351 + type: 'template',
1352 + altText: 'this is a template',
1353 + template: {
1354 + type: 'buttons',
1355 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1356 + imageAspectRatio: 'rectangle',
1357 + imageSize: 'cover',
1358 + imageBackgroundColor: '#FFFFFF',
1359 + title: 'Menu',
1360 + text: 'Please select',
1361 + defaultAction: {
1362 + type: 'uri',
1363 + label: 'View detail',
1364 + uri: 'http://example.com/page/123',
1365 + },
1366 + actions: [
1367 + {
1368 + type: 'postback',
1369 + label: 'Buy',
1370 + data: 'action=buy&itemid=123',
1371 + },
1372 + {
1373 + type: 'postback',
1374 + label: 'Add to cart',
1375 + data: 'action=add&itemid=123',
1376 + },
1377 + {
1378 + type: 'uri',
1379 + label: 'View detail',
1380 + uri: 'http://example.com/page/123',
1381 + },
1382 + ],
1383 + },
1384 + },
1385 + ],
1386 + });
1387 + expect(config.headers).toEqual(headers);
1388 + return [200, reply];
1389 + });
1390 +
1391 + const res = await client.replyButtonTemplate(
1392 + REPLY_TOKEN,
1393 + 'this is a template',
1394 + {
1395 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1396 + imageAspectRatio: 'rectangle',
1397 + imageSize: 'cover',
1398 + imageBackgroundColor: '#FFFFFF',
1399 + title: 'Menu',
1400 + text: 'Please select',
1401 + defaultAction: {
1402 + type: 'uri',
1403 + label: 'View detail',
1404 + uri: 'http://example.com/page/123',
1405 + },
1406 + actions: [
1407 + {
1408 + type: 'postback',
1409 + label: 'Buy',
1410 + data: 'action=buy&itemid=123',
1411 + },
1412 + {
1413 + type: 'postback',
1414 + label: 'Add to cart',
1415 + data: 'action=add&itemid=123',
1416 + },
1417 + {
1418 + type: 'uri',
1419 + label: 'View detail',
1420 + uri: 'http://example.com/page/123',
1421 + },
1422 + ],
1423 + },
1424 + { accessToken: CUSTOM_ACCESS_TOKEN }
1425 + );
1426 +
1427 + expect(res).toEqual(reply);
1428 + });
1429 +
1430 + it('should support replyButtonsTemplate alias', async () => {
1431 + expect.assertions(4);
1432 +
1433 + const { client, mock, headers } = createMock();
1434 +
1435 + const reply = {};
1436 +
1437 + mock.onPost().reply(config => {
1438 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1439 + expect(JSON.parse(config.data)).toEqual({
1440 + replyToken: REPLY_TOKEN,
1441 + messages: [
1442 + {
1443 + type: 'template',
1444 + altText: 'this is a template',
1445 + template: {
1446 + type: 'buttons',
1447 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1448 + imageAspectRatio: 'rectangle',
1449 + imageSize: 'cover',
1450 + imageBackgroundColor: '#FFFFFF',
1451 + title: 'Menu',
1452 + text: 'Please select',
1453 + actions: [
1454 + {
1455 + type: 'postback',
1456 + label: 'Buy',
1457 + data: 'action=buy&itemid=123',
1458 + },
1459 + {
1460 + type: 'postback',
1461 + label: 'Add to cart',
1462 + data: 'action=add&itemid=123',
1463 + },
1464 + {
1465 + type: 'uri',
1466 + label: 'View detail',
1467 + uri: 'http://example.com/page/123',
1468 + },
1469 + ],
1470 + },
1471 + },
1472 + ],
1473 + });
1474 + expect(config.headers).toEqual(headers);
1475 + return [200, reply];
1476 + });
1477 +
1478 + const res = await client.replyButtonsTemplate(
1479 + REPLY_TOKEN,
1480 + 'this is a template',
1481 + {
1482 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1483 + imageAspectRatio: 'rectangle',
1484 + imageSize: 'cover',
1485 + imageBackgroundColor: '#FFFFFF',
1486 + title: 'Menu',
1487 + text: 'Please select',
1488 + actions: [
1489 + {
1490 + type: 'postback',
1491 + label: 'Buy',
1492 + data: 'action=buy&itemid=123',
1493 + },
1494 + {
1495 + type: 'postback',
1496 + label: 'Add to cart',
1497 + data: 'action=add&itemid=123',
1498 + },
1499 + {
1500 + type: 'uri',
1501 + label: 'View detail',
1502 + uri: 'http://example.com/page/123',
1503 + },
1504 + ],
1505 + }
1506 + );
1507 +
1508 + expect(res).toEqual(reply);
1509 + });
1510 +
1511 + it('replyButtonsTemplate alias should work with custom access token', async () => {
1512 + expect.assertions(4);
1513 +
1514 + const { client, mock, headers } = createMock({
1515 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1516 + });
1517 +
1518 + const reply = {};
1519 +
1520 + mock.onPost().reply(config => {
1521 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1522 + expect(JSON.parse(config.data)).toEqual({
1523 + replyToken: REPLY_TOKEN,
1524 + messages: [
1525 + {
1526 + type: 'template',
1527 + altText: 'this is a template',
1528 + template: {
1529 + type: 'buttons',
1530 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1531 + imageAspectRatio: 'rectangle',
1532 + imageSize: 'cover',
1533 + imageBackgroundColor: '#FFFFFF',
1534 + title: 'Menu',
1535 + text: 'Please select',
1536 + actions: [
1537 + {
1538 + type: 'postback',
1539 + label: 'Buy',
1540 + data: 'action=buy&itemid=123',
1541 + },
1542 + {
1543 + type: 'postback',
1544 + label: 'Add to cart',
1545 + data: 'action=add&itemid=123',
1546 + },
1547 + {
1548 + type: 'uri',
1549 + label: 'View detail',
1550 + uri: 'http://example.com/page/123',
1551 + },
1552 + ],
1553 + },
1554 + },
1555 + ],
1556 + });
1557 + expect(config.headers).toEqual(headers);
1558 + return [200, reply];
1559 + });
1560 +
1561 + const res = await client.replyButtonsTemplate(
1562 + REPLY_TOKEN,
1563 + 'this is a template',
1564 + {
1565 + thumbnailImageUrl: 'https://example.com/bot/images/image.jpg',
1566 + imageAspectRatio: 'rectangle',
1567 + imageSize: 'cover',
1568 + imageBackgroundColor: '#FFFFFF',
1569 + title: 'Menu',
1570 + text: 'Please select',
1571 + actions: [
1572 + {
1573 + type: 'postback',
1574 + label: 'Buy',
1575 + data: 'action=buy&itemid=123',
1576 + },
1577 + {
1578 + type: 'postback',
1579 + label: 'Add to cart',
1580 + data: 'action=add&itemid=123',
1581 + },
1582 + {
1583 + type: 'uri',
1584 + label: 'View detail',
1585 + uri: 'http://example.com/page/123',
1586 + },
1587 + ],
1588 + },
1589 + { accessToken: CUSTOM_ACCESS_TOKEN }
1590 + );
1591 +
1592 + expect(res).toEqual(reply);
1593 + });
1594 + });
1595 +
1596 + describe('#replyConfirmTemplate', () => {
1597 + it('should call reply api', async () => {
1598 + expect.assertions(4);
1599 +
1600 + const { client, mock, headers } = createMock();
1601 +
1602 + const reply = {};
1603 +
1604 + mock.onPost().reply(config => {
1605 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1606 + expect(JSON.parse(config.data)).toEqual({
1607 + replyToken: REPLY_TOKEN,
1608 + messages: [
1609 + {
1610 + type: 'template',
1611 + altText: 'this is a confirm template',
1612 + template: {
1613 + type: 'confirm',
1614 + text: 'Are you sure?',
1615 + actions: [
1616 + {
1617 + type: 'message',
1618 + label: 'Yes',
1619 + text: 'yes',
1620 + },
1621 + {
1622 + type: 'message',
1623 + label: 'No',
1624 + text: 'no',
1625 + },
1626 + ],
1627 + },
1628 + },
1629 + ],
1630 + });
1631 + expect(config.headers).toEqual(headers);
1632 + return [200, reply];
1633 + });
1634 +
1635 + const res = await client.replyConfirmTemplate(
1636 + REPLY_TOKEN,
1637 + 'this is a confirm template',
1638 + {
1639 + text: 'Are you sure?',
1640 + actions: [
1641 + {
1642 + type: 'message',
1643 + label: 'Yes',
1644 + text: 'yes',
1645 + },
1646 + {
1647 + type: 'message',
1648 + label: 'No',
1649 + text: 'no',
1650 + },
1651 + ],
1652 + }
1653 + );
1654 +
1655 + expect(res).toEqual(reply);
1656 + });
1657 +
1658 + it('should work with custom access token', async () => {
1659 + expect.assertions(4);
1660 +
1661 + const { client, mock, headers } = createMock({
1662 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1663 + });
1664 +
1665 + const reply = {};
1666 +
1667 + mock.onPost().reply(config => {
1668 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1669 + expect(JSON.parse(config.data)).toEqual({
1670 + replyToken: REPLY_TOKEN,
1671 + messages: [
1672 + {
1673 + type: 'template',
1674 + altText: 'this is a confirm template',
1675 + template: {
1676 + type: 'confirm',
1677 + text: 'Are you sure?',
1678 + actions: [
1679 + {
1680 + type: 'message',
1681 + label: 'Yes',
1682 + text: 'yes',
1683 + },
1684 + {
1685 + type: 'message',
1686 + label: 'No',
1687 + text: 'no',
1688 + },
1689 + ],
1690 + },
1691 + },
1692 + ],
1693 + });
1694 + expect(config.headers).toEqual(headers);
1695 + return [200, reply];
1696 + });
1697 +
1698 + const res = await client.replyConfirmTemplate(
1699 + REPLY_TOKEN,
1700 + 'this is a confirm template',
1701 + {
1702 + text: 'Are you sure?',
1703 + actions: [
1704 + {
1705 + type: 'message',
1706 + label: 'Yes',
1707 + text: 'yes',
1708 + },
1709 + {
1710 + type: 'message',
1711 + label: 'No',
1712 + text: 'no',
1713 + },
1714 + ],
1715 + },
1716 + { accessToken: CUSTOM_ACCESS_TOKEN }
1717 + );
1718 +
1719 + expect(res).toEqual(reply);
1720 + });
1721 + });
1722 +
1723 + describe('#replyCarouselTemplate', () => {
1724 + it('should call reply api', async () => {
1725 + expect.assertions(4);
1726 +
1727 + const { client, mock, headers } = createMock();
1728 +
1729 + const reply = {};
1730 +
1731 + mock.onPost().reply(config => {
1732 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1733 + expect(JSON.parse(config.data)).toEqual({
1734 + replyToken: REPLY_TOKEN,
1735 + messages: [
1736 + {
1737 + type: 'template',
1738 + altText: 'this is a carousel template',
1739 + template: {
1740 + type: 'carousel',
1741 + imageAspectRatio: 'rectangle',
1742 + imageSize: 'cover',
1743 + columns: [
1744 + {
1745 + thumbnailImageUrl:
1746 + 'https://example.com/bot/images/item1.jpg',
1747 + title: 'this is menu',
1748 + text: 'description',
1749 + actions: [
1750 + {
1751 + type: 'postback',
1752 + label: 'Buy',
1753 + data: 'action=buy&itemid=111',
1754 + },
1755 + {
1756 + type: 'postback',
1757 + label: 'Add to cart',
1758 + data: 'action=add&itemid=111',
1759 + },
1760 + {
1761 + type: 'uri',
1762 + label: 'View detail',
1763 + uri: 'http://example.com/page/111',
1764 + },
1765 + ],
1766 + },
1767 + {
1768 + thumbnailImageUrl:
1769 + 'https://example.com/bot/images/item2.jpg',
1770 + title: 'this is menu',
1771 + text: 'description',
1772 + actions: [
1773 + {
1774 + type: 'postback',
1775 + label: 'Buy',
1776 + data: 'action=buy&itemid=222',
1777 + },
1778 + {
1779 + type: 'postback',
1780 + label: 'Add to cart',
1781 + data: 'action=add&itemid=222',
1782 + },
1783 + {
1784 + type: 'uri',
1785 + label: 'View detail',
1786 + uri: 'http://example.com/page/222',
1787 + },
1788 + ],
1789 + },
1790 + ],
1791 + },
1792 + },
1793 + ],
1794 + });
1795 + expect(config.headers).toEqual(headers);
1796 + return [200, reply];
1797 + });
1798 +
1799 + const res = await client.replyCarouselTemplate(
1800 + REPLY_TOKEN,
1801 + 'this is a carousel template',
1802 + [
1803 + {
1804 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1805 + title: 'this is menu',
1806 + text: 'description',
1807 + actions: [
1808 + {
1809 + type: 'postback',
1810 + label: 'Buy',
1811 + data: 'action=buy&itemid=111',
1812 + },
1813 + {
1814 + type: 'postback',
1815 + label: 'Add to cart',
1816 + data: 'action=add&itemid=111',
1817 + },
1818 + {
1819 + type: 'uri',
1820 + label: 'View detail',
1821 + uri: 'http://example.com/page/111',
1822 + },
1823 + ],
1824 + },
1825 + {
1826 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1827 + title: 'this is menu',
1828 + text: 'description',
1829 + actions: [
1830 + {
1831 + type: 'postback',
1832 + label: 'Buy',
1833 + data: 'action=buy&itemid=222',
1834 + },
1835 + {
1836 + type: 'postback',
1837 + label: 'Add to cart',
1838 + data: 'action=add&itemid=222',
1839 + },
1840 + {
1841 + type: 'uri',
1842 + label: 'View detail',
1843 + uri: 'http://example.com/page/222',
1844 + },
1845 + ],
1846 + },
1847 + ],
1848 + {
1849 + imageAspectRatio: 'rectangle',
1850 + imageSize: 'cover',
1851 + }
1852 + );
1853 +
1854 + expect(res).toEqual(reply);
1855 + });
1856 +
1857 + it('should work with custom access token', async () => {
1858 + expect.assertions(4);
1859 +
1860 + const { client, mock, headers } = createMock({
1861 + customAccessToken: CUSTOM_ACCESS_TOKEN,
1862 + });
1863 +
1864 + const reply = {};
1865 +
1866 + mock.onPost().reply(config => {
1867 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
1868 + expect(JSON.parse(config.data)).toEqual({
1869 + replyToken: REPLY_TOKEN,
1870 + messages: [
1871 + {
1872 + type: 'template',
1873 + altText: 'this is a carousel template',
1874 + template: {
1875 + type: 'carousel',
1876 + imageAspectRatio: 'rectangle',
1877 + imageSize: 'cover',
1878 + columns: [
1879 + {
1880 + thumbnailImageUrl:
1881 + 'https://example.com/bot/images/item1.jpg',
1882 + title: 'this is menu',
1883 + text: 'description',
1884 + actions: [
1885 + {
1886 + type: 'postback',
1887 + label: 'Buy',
1888 + data: 'action=buy&itemid=111',
1889 + },
1890 + {
1891 + type: 'postback',
1892 + label: 'Add to cart',
1893 + data: 'action=add&itemid=111',
1894 + },
1895 + {
1896 + type: 'uri',
1897 + label: 'View detail',
1898 + uri: 'http://example.com/page/111',
1899 + },
1900 + ],
1901 + },
1902 + {
1903 + thumbnailImageUrl:
1904 + 'https://example.com/bot/images/item2.jpg',
1905 + title: 'this is menu',
1906 + text: 'description',
1907 + actions: [
1908 + {
1909 + type: 'postback',
1910 + label: 'Buy',
1911 + data: 'action=buy&itemid=222',
1912 + },
1913 + {
1914 + type: 'postback',
1915 + label: 'Add to cart',
1916 + data: 'action=add&itemid=222',
1917 + },
1918 + {
1919 + type: 'uri',
1920 + label: 'View detail',
1921 + uri: 'http://example.com/page/222',
1922 + },
1923 + ],
1924 + },
1925 + ],
1926 + },
1927 + },
1928 + ],
1929 + });
1930 + expect(config.headers).toEqual(headers);
1931 + return [200, reply];
1932 + });
1933 +
1934 + const res = await client.replyCarouselTemplate(
1935 + REPLY_TOKEN,
1936 + 'this is a carousel template',
1937 + [
1938 + {
1939 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
1940 + title: 'this is menu',
1941 + text: 'description',
1942 + actions: [
1943 + {
1944 + type: 'postback',
1945 + label: 'Buy',
1946 + data: 'action=buy&itemid=111',
1947 + },
1948 + {
1949 + type: 'postback',
1950 + label: 'Add to cart',
1951 + data: 'action=add&itemid=111',
1952 + },
1953 + {
1954 + type: 'uri',
1955 + label: 'View detail',
1956 + uri: 'http://example.com/page/111',
1957 + },
1958 + ],
1959 + },
1960 + {
1961 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
1962 + title: 'this is menu',
1963 + text: 'description',
1964 + actions: [
1965 + {
1966 + type: 'postback',
1967 + label: 'Buy',
1968 + data: 'action=buy&itemid=222',
1969 + },
1970 + {
1971 + type: 'postback',
1972 + label: 'Add to cart',
1973 + data: 'action=add&itemid=222',
1974 + },
1975 + {
1976 + type: 'uri',
1977 + label: 'View detail',
1978 + uri: 'http://example.com/page/222',
1979 + },
1980 + ],
1981 + },
1982 + ],
1983 + {
1984 + accessToken: CUSTOM_ACCESS_TOKEN,
1985 + imageAspectRatio: 'rectangle',
1986 + imageSize: 'cover',
1987 + }
1988 + );
1989 +
1990 + expect(res).toEqual(reply);
1991 + });
1992 +
1993 + it('should work without option', async () => {
1994 + expect.assertions(4);
1995 +
1996 + const { client, mock, headers } = createMock();
1997 +
1998 + const reply = {};
1999 +
2000 + mock.onPost().reply(config => {
2001 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
2002 + expect(JSON.parse(config.data)).toEqual({
2003 + replyToken: REPLY_TOKEN,
2004 + messages: [
2005 + {
2006 + type: 'template',
2007 + altText: 'this is a carousel template',
2008 + template: {
2009 + type: 'carousel',
2010 + columns: [
2011 + {
2012 + thumbnailImageUrl:
2013 + 'https://example.com/bot/images/item1.jpg',
2014 + title: 'this is menu',
2015 + text: 'description',
2016 + actions: [
2017 + {
2018 + type: 'postback',
2019 + label: 'Buy',
2020 + data: 'action=buy&itemid=111',
2021 + },
2022 + {
2023 + type: 'postback',
2024 + label: 'Add to cart',
2025 + data: 'action=add&itemid=111',
2026 + },
2027 + {
2028 + type: 'uri',
2029 + label: 'View detail',
2030 + uri: 'http://example.com/page/111',
2031 + },
2032 + ],
2033 + },
2034 + {
2035 + thumbnailImageUrl:
2036 + 'https://example.com/bot/images/item2.jpg',
2037 + title: 'this is menu',
2038 + text: 'description',
2039 + actions: [
2040 + {
2041 + type: 'postback',
2042 + label: 'Buy',
2043 + data: 'action=buy&itemid=222',
2044 + },
2045 + {
2046 + type: 'postback',
2047 + label: 'Add to cart',
2048 + data: 'action=add&itemid=222',
2049 + },
2050 + {
2051 + type: 'uri',
2052 + label: 'View detail',
2053 + uri: 'http://example.com/page/222',
2054 + },
2055 + ],
2056 + },
2057 + ],
2058 + },
2059 + },
2060 + ],
2061 + });
2062 + expect(config.headers).toEqual(headers);
2063 + return [200, reply];
2064 + });
2065 +
2066 + const res = await client.replyCarouselTemplate(
2067 + REPLY_TOKEN,
2068 + 'this is a carousel template',
2069 + [
2070 + {
2071 + thumbnailImageUrl: 'https://example.com/bot/images/item1.jpg',
2072 + title: 'this is menu',
2073 + text: 'description',
2074 + actions: [
2075 + {
2076 + type: 'postback',
2077 + label: 'Buy',
2078 + data: 'action=buy&itemid=111',
2079 + },
2080 + {
2081 + type: 'postback',
2082 + label: 'Add to cart',
2083 + data: 'action=add&itemid=111',
2084 + },
2085 + {
2086 + type: 'uri',
2087 + label: 'View detail',
2088 + uri: 'http://example.com/page/111',
2089 + },
2090 + ],
2091 + },
2092 + {
2093 + thumbnailImageUrl: 'https://example.com/bot/images/item2.jpg',
2094 + title: 'this is menu',
2095 + text: 'description',
2096 + actions: [
2097 + {
2098 + type: 'postback',
2099 + label: 'Buy',
2100 + data: 'action=buy&itemid=222',
2101 + },
2102 + {
2103 + type: 'postback',
2104 + label: 'Add to cart',
2105 + data: 'action=add&itemid=222',
2106 + },
2107 + {
2108 + type: 'uri',
2109 + label: 'View detail',
2110 + uri: 'http://example.com/page/222',
2111 + },
2112 + ],
2113 + },
2114 + ]
2115 + );
2116 +
2117 + expect(res).toEqual(reply);
2118 + });
2119 + });
2120 +
2121 + describe('#replyImageCarouselTemplate', () => {
2122 + it('should call reply api', async () => {
2123 + expect.assertions(4);
2124 +
2125 + const { client, mock, headers } = createMock();
2126 +
2127 + const reply = {};
2128 +
2129 + mock.onPost().reply(config => {
2130 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
2131 + expect(JSON.parse(config.data)).toEqual({
2132 + replyToken: REPLY_TOKEN,
2133 + messages: [
2134 + {
2135 + type: 'template',
2136 + altText: 'this is an image carousel template',
2137 + template: {
2138 + type: 'image_carousel',
2139 + columns: [
2140 + {
2141 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2142 + action: {
2143 + type: 'postback',
2144 + label: 'Buy',
2145 + data: 'action=buy&itemid=111',
2146 + },
2147 + },
2148 + {
2149 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2150 + action: {
2151 + type: 'message',
2152 + label: 'Yes',
2153 + text: 'yes',
2154 + },
2155 + },
2156 + {
2157 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2158 + action: {
2159 + type: 'uri',
2160 + label: 'View detail',
2161 + uri: 'http://example.com/page/222',
2162 + },
2163 + },
2164 + ],
2165 + },
2166 + },
2167 + ],
2168 + });
2169 + expect(config.headers).toEqual(headers);
2170 + return [200, reply];
2171 + });
2172 +
2173 + const res = await client.replyImageCarouselTemplate(
2174 + REPLY_TOKEN,
2175 + 'this is an image carousel template',
2176 + [
2177 + {
2178 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2179 + action: {
2180 + type: 'postback',
2181 + label: 'Buy',
2182 + data: 'action=buy&itemid=111',
2183 + },
2184 + },
2185 + {
2186 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2187 + action: {
2188 + type: 'message',
2189 + label: 'Yes',
2190 + text: 'yes',
2191 + },
2192 + },
2193 + {
2194 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2195 + action: {
2196 + type: 'uri',
2197 + label: 'View detail',
2198 + uri: 'http://example.com/page/222',
2199 + },
2200 + },
2201 + ]
2202 + );
2203 +
2204 + expect(res).toEqual(reply);
2205 + });
2206 +
2207 + it('should work with custom access token', async () => {
2208 + expect.assertions(4);
2209 +
2210 + const { client, mock, headers } = createMock({
2211 + customAccessToken: CUSTOM_ACCESS_TOKEN,
2212 + });
2213 +
2214 + const reply = {};
2215 +
2216 + mock.onPost().reply(config => {
2217 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
2218 + expect(JSON.parse(config.data)).toEqual({
2219 + replyToken: REPLY_TOKEN,
2220 + messages: [
2221 + {
2222 + type: 'template',
2223 + altText: 'this is an image carousel template',
2224 + template: {
2225 + type: 'image_carousel',
2226 + columns: [
2227 + {
2228 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2229 + action: {
2230 + type: 'postback',
2231 + label: 'Buy',
2232 + data: 'action=buy&itemid=111',
2233 + },
2234 + },
2235 + {
2236 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2237 + action: {
2238 + type: 'message',
2239 + label: 'Yes',
2240 + text: 'yes',
2241 + },
2242 + },
2243 + {
2244 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2245 + action: {
2246 + type: 'uri',
2247 + label: 'View detail',
2248 + uri: 'http://example.com/page/222',
2249 + },
2250 + },
2251 + ],
2252 + },
2253 + },
2254 + ],
2255 + });
2256 + expect(config.headers).toEqual(headers);
2257 + return [200, reply];
2258 + });
2259 +
2260 + const res = await client.replyImageCarouselTemplate(
2261 + REPLY_TOKEN,
2262 + 'this is an image carousel template',
2263 + [
2264 + {
2265 + imageUrl: 'https://example.com/bot/images/item1.jpg',
2266 + action: {
2267 + type: 'postback',
2268 + label: 'Buy',
2269 + data: 'action=buy&itemid=111',
2270 + },
2271 + },
2272 + {
2273 + imageUrl: 'https://example.com/bot/images/item2.jpg',
2274 + action: {
2275 + type: 'message',
2276 + label: 'Yes',
2277 + text: 'yes',
2278 + },
2279 + },
2280 + {
2281 + imageUrl: 'https://example.com/bot/images/item3.jpg',
2282 + action: {
2283 + type: 'uri',
2284 + label: 'View detail',
2285 + uri: 'http://example.com/page/222',
2286 + },
2287 + },
2288 + ],
2289 + { accessToken: CUSTOM_ACCESS_TOKEN }
2290 + );
2291 +
2292 + expect(res).toEqual(reply);
2293 + });
2294 + });
2295 +
2296 + describe('#replyFlex', () => {
2297 + it('should call reply api', async () => {
2298 + expect.assertions(4);
2299 +
2300 + const { client, mock, headers } = createMock();
2301 +
2302 + const reply = {};
2303 +
2304 + mock.onPost().reply(config => {
2305 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
2306 + expect(JSON.parse(config.data)).toEqual({
2307 + replyToken: REPLY_TOKEN,
2308 + messages: [
2309 + {
2310 + type: 'flex',
2311 + altText: 'this is a flex message',
2312 + contents: {
2313 + type: 'bubble',
2314 + header: {
2315 + type: 'box',
2316 + layout: 'vertical',
2317 + contents: [
2318 + {
2319 + type: 'text',
2320 + text: 'Header text',
2321 + },
2322 + ],
2323 + },
2324 + hero: {
2325 + type: 'image',
2326 + url: 'https://example.com/flex/images/image.jpg',
2327 + },
2328 + body: {
2329 + type: 'box',
2330 + layout: 'vertical',
2331 + contents: [
2332 + {
2333 + type: 'text',
2334 + text: 'Body text',
2335 + },
2336 + ],
2337 + },
2338 + footer: {
2339 + type: 'box',
2340 + layout: 'vertical',
2341 + contents: [
2342 + {
2343 + type: 'text',
2344 + text: 'Footer text',
2345 + },
2346 + ],
2347 + },
2348 + styles: {
2349 + comment: 'See the example of a bubble style object',
2350 + },
2351 + },
2352 + },
2353 + ],
2354 + });
2355 + expect(config.headers).toEqual(headers);
2356 + return [200, reply];
2357 + });
2358 +
2359 + const res = await client.replyFlex(
2360 + REPLY_TOKEN,
2361 + 'this is a flex message',
2362 + {
2363 + type: 'bubble',
2364 + header: {
2365 + type: 'box',
2366 + layout: 'vertical',
2367 + contents: [
2368 + {
2369 + type: 'text',
2370 + text: 'Header text',
2371 + },
2372 + ],
2373 + },
2374 + hero: {
2375 + type: 'image',
2376 + url: 'https://example.com/flex/images/image.jpg',
2377 + },
2378 + body: {
2379 + type: 'box',
2380 + layout: 'vertical',
2381 + contents: [
2382 + {
2383 + type: 'text',
2384 + text: 'Body text',
2385 + },
2386 + ],
2387 + },
2388 + footer: {
2389 + type: 'box',
2390 + layout: 'vertical',
2391 + contents: [
2392 + {
2393 + type: 'text',
2394 + text: 'Footer text',
2395 + },
2396 + ],
2397 + },
2398 + styles: {
2399 + comment: 'See the example of a bubble style object',
2400 + },
2401 + }
2402 + );
2403 +
2404 + expect(res).toEqual(reply);
2405 + });
2406 + it('should call reply api', async () => {
2407 + expect.assertions(4);
2408 +
2409 + const { client, mock, headers } = createMock({
2410 + customAccessToken: CUSTOM_ACCESS_TOKEN,
2411 + });
2412 +
2413 + const reply = {};
2414 +
2415 + mock.onPost().reply(config => {
2416 + expect(config.url).toEqual('https://api.line.me/v2/bot/message/reply');
2417 + expect(JSON.parse(config.data)).toEqual({
2418 + replyToken: REPLY_TOKEN,
2419 + messages: [
2420 + {
2421 + type: 'flex',
2422 + altText: 'this is a flex message',
2423 + contents: {
2424 + type: 'bubble',
2425 + header: {
2426 + type: 'box',
2427 + layout: 'vertical',
2428 + contents: [
2429 + {
2430 + type: 'text',
2431 + text: 'Header text',
2432 + },
2433 + ],
2434 + },
2435 + hero: {
2436 + type: 'image',
2437 + url: 'https://example.com/flex/images/image.jpg',
2438 + },
2439 + body: {
2440 + type: 'box',
2441 + layout: 'vertical',
2442 + contents: [
2443 + {
2444 + type: 'text',
2445 + text: 'Body text',
2446 + },
2447 + ],
2448 + },
2449 + footer: {
2450 + type: 'box',
2451 + layout: 'vertical',
2452 + contents: [
2453 + {
2454 + type: 'text',
2455 + text: 'Footer text',
2456 + },
2457 + ],
2458 + },
2459 + styles: {
2460 + comment: 'See the example of a bubble style object',
2461 + },
2462 + },
2463 + },
2464 + ],
2465 + });
2466 + expect(config.headers).toEqual(headers);
2467 + return [200, reply];
2468 + });
2469 +
2470 + const res = await client.replyFlex(
2471 + REPLY_TOKEN,
2472 + 'this is a flex message',
2473 + {
2474 + type: 'bubble',
2475 + header: {
2476 + type: 'box',
2477 + layout: 'vertical',
2478 + contents: [
2479 + {
2480 + type: 'text',
2481 + text: 'Header text',
2482 + },
2483 + ],
2484 + },
2485 + hero: {
2486 + type: 'image',
2487 + url: 'https://example.com/flex/images/image.jpg',
2488 + },
2489 + body: {
2490 + type: 'box',
2491 + layout: 'vertical',
2492 + contents: [
2493 + {
2494 + type: 'text',
2495 + text: 'Body text',
2496 + },
2497 + ],
2498 + },
2499 + footer: {
2500 + type: 'box',
2501 + layout: 'vertical',
2502 + contents: [
2503 + {
2504 + type: 'text',
2505 + text: 'Footer text',
2506 + },
2507 + ],
2508 + },
2509 + styles: {
2510 + comment: 'See the example of a bubble style object',
2511 + },
2512 + },
2513 + { accessToken: CUSTOM_ACCESS_TOKEN }
2514 + );
2515 +
2516 + expect(res).toEqual(reply);
2517 + });
2518 + });
2519 +});
1 +import MockAdapter from 'axios-mock-adapter';
2 +
3 +import LineClient from '../LineClient';
4 +
5 +const RECIPIENT_ID = '1QAZ2WSX';
6 +const GROUP_ID = 'G1QAZ2WSX';
7 +const ROOM_ID = 'R1QAZ2WSX';
8 +const CUSTOM_ACCESS_TOKEN = '555555555';
9 +const ACCESS_TOKEN = '1234567890';
10 +const CHANNEL_SECRET = 'so-secret';
11 +
12 +const createMock = ({ customAccessToken } = {}) => {
13 + const client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
14 + const mock = new MockAdapter(client.axios);
15 + const headers = {
16 + Accept: 'application/json, text/plain, */*',
17 + 'Content-Type': 'application/json',
18 + Authorization: `Bearer ${customAccessToken || ACCESS_TOKEN}`,
19 + };
20 + return { client, mock, headers };
21 +};
22 +
23 +describe('Group/Room Member', () => {
24 + describe('#getGroupMemberProfile', () => {
25 + it('should response group member profile', async () => {
26 + expect.assertions(4);
27 +
28 + const { client, mock, headers } = createMock();
29 + const reply = {
30 + displayName: 'LINE taro',
31 + userId: RECIPIENT_ID,
32 + pictureUrl: 'http://obs.line-apps.com/...',
33 + };
34 +
35 + mock.onGet().reply(config => {
36 + expect(config.url).toEqual(
37 + `https://api.line.me/v2/bot/group/${GROUP_ID}/member/${RECIPIENT_ID}`
38 + );
39 + expect(config.data).toEqual(undefined);
40 + expect(config.headers).toEqual(headers);
41 + return [200, reply];
42 + });
43 +
44 + const res = await client.getGroupMemberProfile(GROUP_ID, RECIPIENT_ID);
45 +
46 + expect(res).toEqual(reply);
47 + });
48 +
49 + it('should work with custom access token', async () => {
50 + expect.assertions(4);
51 +
52 + const { client, mock, headers } = createMock({
53 + customAccessToken: CUSTOM_ACCESS_TOKEN,
54 + });
55 + const reply = {
56 + displayName: 'LINE taro',
57 + userId: RECIPIENT_ID,
58 + pictureUrl: 'http://obs.line-apps.com/...',
59 + };
60 +
61 + mock.onGet().reply(config => {
62 + expect(config.url).toEqual(
63 + `https://api.line.me/v2/bot/group/${GROUP_ID}/member/${RECIPIENT_ID}`
64 + );
65 + expect(config.data).toEqual(undefined);
66 + expect(config.headers).toEqual(headers);
67 + return [200, reply];
68 + });
69 +
70 + const res = await client.getGroupMemberProfile(GROUP_ID, RECIPIENT_ID, {
71 + accessToken: CUSTOM_ACCESS_TOKEN,
72 + });
73 +
74 + expect(res).toEqual(reply);
75 + });
76 + });
77 +
78 + describe('#getRoomMemberProfile', () => {
79 + it('should response room member profile', async () => {
80 + expect.assertions(4);
81 +
82 + const { client, mock, headers } = createMock();
83 + const reply = {
84 + displayName: 'LINE taro',
85 + userId: RECIPIENT_ID,
86 + pictureUrl: 'http://obs.line-apps.com/...',
87 + };
88 +
89 + mock.onGet().reply(config => {
90 + expect(config.url).toEqual(
91 + `https://api.line.me/v2/bot/room/${ROOM_ID}/member/${RECIPIENT_ID}`
92 + );
93 + expect(config.data).toEqual(undefined);
94 + expect(config.headers).toEqual(headers);
95 + return [200, reply];
96 + });
97 +
98 + const res = await client.getRoomMemberProfile(ROOM_ID, RECIPIENT_ID);
99 +
100 + expect(res).toEqual(reply);
101 + });
102 +
103 + it('should work with custom access token', async () => {
104 + expect.assertions(4);
105 +
106 + const { client, mock, headers } = createMock({
107 + customAccessToken: CUSTOM_ACCESS_TOKEN,
108 + });
109 + const reply = {
110 + displayName: 'LINE taro',
111 + userId: RECIPIENT_ID,
112 + pictureUrl: 'http://obs.line-apps.com/...',
113 + };
114 +
115 + mock.onGet().reply(config => {
116 + expect(config.url).toEqual(
117 + `https://api.line.me/v2/bot/room/${ROOM_ID}/member/${RECIPIENT_ID}`
118 + );
119 + expect(config.data).toEqual(undefined);
120 + expect(config.headers).toEqual(headers);
121 + return [200, reply];
122 + });
123 +
124 + const res = await client.getRoomMemberProfile(ROOM_ID, RECIPIENT_ID, {
125 + accessToken: CUSTOM_ACCESS_TOKEN,
126 + });
127 +
128 + expect(res).toEqual(reply);
129 + });
130 + });
131 +
132 + describe('#getGroupMemberIds', () => {
133 + it('should response group member ids', async () => {
134 + expect.assertions(4);
135 +
136 + const { client, mock, headers } = createMock();
137 + const reply = {
138 + memberIds: [
139 + 'Uxxxxxxxxxxxxxx...',
140 + 'Uxxxxxxxxxxxxxx...',
141 + 'Uxxxxxxxxxxxxxx...',
142 + ],
143 + };
144 +
145 + mock.onGet().reply(config => {
146 + expect(config.url).toEqual(
147 + `https://api.line.me/v2/bot/group/${GROUP_ID}/members/ids`
148 + );
149 + expect(config.data).toEqual(undefined);
150 + expect(config.headers).toEqual(headers);
151 + return [200, reply];
152 + });
153 +
154 + const res = await client.getGroupMemberIds(GROUP_ID);
155 +
156 + expect(res).toEqual(reply);
157 + });
158 +
159 + it('should work with custom access token', async () => {
160 + expect.assertions(4);
161 +
162 + const { client, mock, headers } = createMock({
163 + customAccessToken: CUSTOM_ACCESS_TOKEN,
164 + });
165 + const reply = {
166 + memberIds: [
167 + 'Uxxxxxxxxxxxxxx...',
168 + 'Uxxxxxxxxxxxxxx...',
169 + 'Uxxxxxxxxxxxxxx...',
170 + ],
171 + };
172 +
173 + mock.onGet().reply(config => {
174 + expect(config.url).toEqual(
175 + `https://api.line.me/v2/bot/group/${GROUP_ID}/members/ids`
176 + );
177 + expect(config.data).toEqual(undefined);
178 + expect(config.headers).toEqual(headers);
179 + return [200, reply];
180 + });
181 +
182 + const res = await client.getGroupMemberIds(GROUP_ID, null, {
183 + accessToken: CUSTOM_ACCESS_TOKEN,
184 + });
185 +
186 + expect(res).toEqual(reply);
187 + });
188 +
189 + it('should call api with provided continuationToken', async () => {
190 + expect.assertions(4);
191 +
192 + const { client, mock, headers } = createMock();
193 + const reply = {
194 + memberIds: [
195 + 'Uxxxxxxxxxxxxxx...',
196 + 'Uxxxxxxxxxxxxxx...',
197 + 'Uxxxxxxxxxxxxxx...',
198 + ],
199 + };
200 +
201 + const continuationToken = 'TOKEN';
202 +
203 + mock.onGet().reply(config => {
204 + expect(config.url).toEqual(
205 + `https://api.line.me/v2/bot/group/${GROUP_ID}/members/ids?start=${continuationToken}`
206 + );
207 + expect(config.data).toEqual(undefined);
208 + expect(config.headers).toEqual(headers);
209 + return [200, reply];
210 + });
211 +
212 + const res = await client.getGroupMemberIds(GROUP_ID, continuationToken);
213 +
214 + expect(res).toEqual(reply);
215 + });
216 + });
217 +
218 + describe('#getAllGroupMemberIds', () => {
219 + it('should fetch all member ids until it is finished', async () => {
220 + expect.assertions(7);
221 +
222 + const { client, mock, headers } = createMock();
223 + const continuationToken = 'TOKEN';
224 + const reply1 = {
225 + memberIds: [
226 + 'Uxxxxxxxxxxxxxx..1',
227 + 'Uxxxxxxxxxxxxxx..2',
228 + 'Uxxxxxxxxxxxxxx..3',
229 + ],
230 + next: continuationToken,
231 + };
232 + const reply2 = {
233 + memberIds: [
234 + 'Uxxxxxxxxxxxxxx..4',
235 + 'Uxxxxxxxxxxxxxx..5',
236 + 'Uxxxxxxxxxxxxxx..6',
237 + ],
238 + };
239 +
240 + mock
241 + .onGet(`/v2/bot/group/${GROUP_ID}/members/ids`)
242 + .replyOnce(config => {
243 + expect(config.url).toEqual(
244 + `https://api.line.me/v2/bot/group/${GROUP_ID}/members/ids`
245 + );
246 + expect(config.data).toEqual(undefined);
247 + expect(config.headers).toEqual(headers);
248 + return [200, reply1];
249 + })
250 + .onGet(
251 + `/v2/bot/group/${GROUP_ID}/members/ids?start=${continuationToken}`
252 + )
253 + .replyOnce(config => {
254 + expect(config.url).toEqual(
255 + `https://api.line.me/v2/bot/group/${GROUP_ID}/members/ids?start=${continuationToken}`
256 + );
257 + expect(config.data).toEqual(undefined);
258 + expect(config.headers).toEqual(headers);
259 + return [200, reply2];
260 + });
261 +
262 + const res = await client.getAllGroupMemberIds(GROUP_ID);
263 +
264 + expect(res).toEqual([
265 + 'Uxxxxxxxxxxxxxx..1',
266 + 'Uxxxxxxxxxxxxxx..2',
267 + 'Uxxxxxxxxxxxxxx..3',
268 + 'Uxxxxxxxxxxxxxx..4',
269 + 'Uxxxxxxxxxxxxxx..5',
270 + 'Uxxxxxxxxxxxxxx..6',
271 + ]);
272 + });
273 +
274 + it('should work with custom access token', async () => {
275 + expect.assertions(7);
276 +
277 + const { client, mock, headers } = createMock({
278 + customAccessToken: CUSTOM_ACCESS_TOKEN,
279 + });
280 + const continuationToken = 'TOKEN';
281 + const reply1 = {
282 + memberIds: [
283 + 'Uxxxxxxxxxxxxxx..1',
284 + 'Uxxxxxxxxxxxxxx..2',
285 + 'Uxxxxxxxxxxxxxx..3',
286 + ],
287 + next: continuationToken,
288 + };
289 + const reply2 = {
290 + memberIds: [
291 + 'Uxxxxxxxxxxxxxx..4',
292 + 'Uxxxxxxxxxxxxxx..5',
293 + 'Uxxxxxxxxxxxxxx..6',
294 + ],
295 + };
296 +
297 + mock
298 + .onGet(`/v2/bot/group/${GROUP_ID}/members/ids`)
299 + .replyOnce(config => {
300 + expect(config.url).toEqual(
301 + `https://api.line.me/v2/bot/group/${GROUP_ID}/members/ids`
302 + );
303 + expect(config.data).toEqual(undefined);
304 + expect(config.headers).toEqual(headers);
305 + return [200, reply1];
306 + })
307 + .onGet(
308 + `/v2/bot/group/${GROUP_ID}/members/ids?start=${continuationToken}`
309 + )
310 + .replyOnce(config => {
311 + expect(config.url).toEqual(
312 + `https://api.line.me/v2/bot/group/${GROUP_ID}/members/ids?start=${continuationToken}`
313 + );
314 + expect(config.data).toEqual(undefined);
315 + expect(config.headers).toEqual(headers);
316 + return [200, reply2];
317 + });
318 +
319 + const res = await client.getAllGroupMemberIds(GROUP_ID, {
320 + accessToken: CUSTOM_ACCESS_TOKEN,
321 + });
322 +
323 + expect(res).toEqual([
324 + 'Uxxxxxxxxxxxxxx..1',
325 + 'Uxxxxxxxxxxxxxx..2',
326 + 'Uxxxxxxxxxxxxxx..3',
327 + 'Uxxxxxxxxxxxxxx..4',
328 + 'Uxxxxxxxxxxxxxx..5',
329 + 'Uxxxxxxxxxxxxxx..6',
330 + ]);
331 + });
332 + });
333 +
334 + describe('#getRoomMemberIds', () => {
335 + it('should response room member ids', async () => {
336 + expect.assertions(4);
337 +
338 + const { client, mock, headers } = createMock();
339 + const reply = {
340 + memberIds: [
341 + 'Uxxxxxxxxxxxxxx...',
342 + 'Uxxxxxxxxxxxxxx...',
343 + 'Uxxxxxxxxxxxxxx...',
344 + ],
345 + };
346 +
347 + mock.onGet().reply(config => {
348 + expect(config.url).toEqual(
349 + `https://api.line.me/v2/bot/room/${ROOM_ID}/members/ids`
350 + );
351 + expect(config.data).toEqual(undefined);
352 + expect(config.headers).toEqual(headers);
353 + return [200, reply];
354 + });
355 +
356 + const res = await client.getRoomMemberIds(ROOM_ID);
357 +
358 + expect(res).toEqual(reply);
359 + });
360 +
361 + it('should work with custom access token', async () => {
362 + expect.assertions(4);
363 +
364 + const { client, mock, headers } = createMock({
365 + customAccessToken: CUSTOM_ACCESS_TOKEN,
366 + });
367 + const reply = {
368 + memberIds: [
369 + 'Uxxxxxxxxxxxxxx...',
370 + 'Uxxxxxxxxxxxxxx...',
371 + 'Uxxxxxxxxxxxxxx...',
372 + ],
373 + };
374 +
375 + mock.onGet().reply(config => {
376 + expect(config.url).toEqual(
377 + `https://api.line.me/v2/bot/room/${ROOM_ID}/members/ids`
378 + );
379 + expect(config.data).toEqual(undefined);
380 + expect(config.headers).toEqual(headers);
381 + return [200, reply];
382 + });
383 +
384 + const res = await client.getRoomMemberIds(ROOM_ID, null, {
385 + accessToken: CUSTOM_ACCESS_TOKEN,
386 + });
387 +
388 + expect(res).toEqual(reply);
389 + });
390 +
391 + it('should call api with provided continuationToken', async () => {
392 + expect.assertions(4);
393 +
394 + const { client, mock, headers } = createMock();
395 + const reply = {
396 + memberIds: [
397 + 'Uxxxxxxxxxxxxxx...',
398 + 'Uxxxxxxxxxxxxxx...',
399 + 'Uxxxxxxxxxxxxxx...',
400 + ],
401 + };
402 +
403 + const continuationToken = 'TOKEN';
404 +
405 + mock.onGet().reply(config => {
406 + expect(config.url).toEqual(
407 + `https://api.line.me/v2/bot/room/${ROOM_ID}/members/ids?start=${continuationToken}`
408 + );
409 + expect(config.data).toEqual(undefined);
410 + expect(config.headers).toEqual(headers);
411 + return [200, reply];
412 + });
413 +
414 + const res = await client.getRoomMemberIds(ROOM_ID, continuationToken);
415 +
416 + expect(res).toEqual(reply);
417 + });
418 + });
419 +
420 + describe('#getAllRoomMemberIds', () => {
421 + it('should fetch all member ids until it is finished', async () => {
422 + expect.assertions(7);
423 +
424 + const { client, mock, headers } = createMock();
425 + const continuationToken = 'TOKEN';
426 + const reply1 = {
427 + memberIds: [
428 + 'Uxxxxxxxxxxxxxx..1',
429 + 'Uxxxxxxxxxxxxxx..2',
430 + 'Uxxxxxxxxxxxxxx..3',
431 + ],
432 + next: continuationToken,
433 + };
434 + const reply2 = {
435 + memberIds: [
436 + 'Uxxxxxxxxxxxxxx..4',
437 + 'Uxxxxxxxxxxxxxx..5',
438 + 'Uxxxxxxxxxxxxxx..6',
439 + ],
440 + };
441 +
442 + mock
443 + .onGet()
444 + .replyOnce(config => {
445 + expect(config.url).toEqual(
446 + `https://api.line.me/v2/bot/room/${ROOM_ID}/members/ids`
447 + );
448 + expect(config.data).toEqual(undefined);
449 + expect(config.headers).toEqual(headers);
450 + return [200, reply1];
451 + })
452 + .onGet()
453 + .replyOnce(config => {
454 + expect(config.url).toEqual(
455 + `https://api.line.me/v2/bot/room/${ROOM_ID}/members/ids?start=${continuationToken}`
456 + );
457 + expect(config.data).toEqual(undefined);
458 + expect(config.headers).toEqual(headers);
459 + return [200, reply2];
460 + });
461 +
462 + const res = await client.getAllRoomMemberIds(ROOM_ID);
463 +
464 + expect(res).toEqual([
465 + 'Uxxxxxxxxxxxxxx..1',
466 + 'Uxxxxxxxxxxxxxx..2',
467 + 'Uxxxxxxxxxxxxxx..3',
468 + 'Uxxxxxxxxxxxxxx..4',
469 + 'Uxxxxxxxxxxxxxx..5',
470 + 'Uxxxxxxxxxxxxxx..6',
471 + ]);
472 + });
473 +
474 + it('should work with custom access token', async () => {
475 + expect.assertions(7);
476 +
477 + const { client, mock, headers } = createMock({
478 + customAccessToken: CUSTOM_ACCESS_TOKEN,
479 + });
480 + const continuationToken = 'TOKEN';
481 + const reply1 = {
482 + memberIds: [
483 + 'Uxxxxxxxxxxxxxx..1',
484 + 'Uxxxxxxxxxxxxxx..2',
485 + 'Uxxxxxxxxxxxxxx..3',
486 + ],
487 + next: continuationToken,
488 + };
489 + const reply2 = {
490 + memberIds: [
491 + 'Uxxxxxxxxxxxxxx..4',
492 + 'Uxxxxxxxxxxxxxx..5',
493 + 'Uxxxxxxxxxxxxxx..6',
494 + ],
495 + };
496 +
497 + mock
498 + .onGet()
499 + .replyOnce(config => {
500 + expect(config.url).toEqual(
501 + `https://api.line.me/v2/bot/room/${ROOM_ID}/members/ids`
502 + );
503 + expect(config.data).toEqual(undefined);
504 + expect(config.headers).toEqual(headers);
505 + return [200, reply1];
506 + })
507 + .onGet()
508 + .replyOnce(config => {
509 + expect(config.url).toEqual(
510 + `https://api.line.me/v2/bot/room/${ROOM_ID}/members/ids?start=${continuationToken}`
511 + );
512 + expect(config.data).toEqual(undefined);
513 + expect(config.headers).toEqual(headers);
514 + return [200, reply2];
515 + });
516 +
517 + const res = await client.getAllRoomMemberIds(ROOM_ID, {
518 + accessToken: CUSTOM_ACCESS_TOKEN,
519 + });
520 +
521 + expect(res).toEqual([
522 + 'Uxxxxxxxxxxxxxx..1',
523 + 'Uxxxxxxxxxxxxxx..2',
524 + 'Uxxxxxxxxxxxxxx..3',
525 + 'Uxxxxxxxxxxxxxx..4',
526 + 'Uxxxxxxxxxxxxxx..5',
527 + 'Uxxxxxxxxxxxxxx..6',
528 + ]);
529 + });
530 + });
531 +});
532 +
533 +describe('Leave', () => {
534 + describe('#leaveGroup', () => {
535 + it('should call leave api', async () => {
536 + expect.assertions(4);
537 +
538 + const { client, mock, headers } = createMock();
539 +
540 + const reply = {};
541 +
542 + mock.onPost().reply(config => {
543 + expect(config.url).toEqual(
544 + `https://api.line.me/v2/bot/group/${GROUP_ID}/leave`
545 + );
546 + expect(config.data).toEqual(null);
547 + expect(config.headers).toEqual(headers);
548 + return [200, reply];
549 + });
550 +
551 + const res = await client.leaveGroup(GROUP_ID);
552 +
553 + expect(res).toEqual(reply);
554 + });
555 +
556 + it('should work with custom access token', async () => {
557 + expect.assertions(4);
558 +
559 + const { client, mock, headers } = createMock({
560 + customAccessToken: CUSTOM_ACCESS_TOKEN,
561 + });
562 +
563 + const reply = {};
564 +
565 + mock.onPost().reply(config => {
566 + expect(config.url).toEqual(
567 + `https://api.line.me/v2/bot/group/${GROUP_ID}/leave`
568 + );
569 + expect(config.data).toEqual(null);
570 + expect(config.headers).toEqual(headers);
571 + return [200, reply];
572 + });
573 +
574 + const res = await client.leaveGroup(GROUP_ID, {
575 + accessToken: CUSTOM_ACCESS_TOKEN,
576 + });
577 +
578 + expect(res).toEqual(reply);
579 + });
580 + });
581 +
582 + describe('#leaveRoom', () => {
583 + it('should call leave api', async () => {
584 + expect.assertions(4);
585 +
586 + const { client, mock, headers } = createMock();
587 +
588 + const reply = {};
589 +
590 + mock.onPost().reply(config => {
591 + expect(config.url).toEqual(
592 + `https://api.line.me/v2/bot/room/${ROOM_ID}/leave`
593 + );
594 + expect(config.data).toEqual(null);
595 + expect(config.headers).toEqual(headers);
596 + return [200, reply];
597 + });
598 +
599 + const res = await client.leaveRoom(ROOM_ID);
600 +
601 + expect(res).toEqual(reply);
602 + });
603 +
604 + it('should work with custom access token', async () => {
605 + expect.assertions(4);
606 +
607 + const { client, mock, headers } = createMock({
608 + customAccessToken: CUSTOM_ACCESS_TOKEN,
609 + });
610 +
611 + const reply = {};
612 +
613 + mock.onPost().reply(config => {
614 + expect(config.url).toEqual(
615 + `https://api.line.me/v2/bot/room/${ROOM_ID}/leave`
616 + );
617 + expect(config.data).toEqual(null);
618 + expect(config.headers).toEqual(headers);
619 + return [200, reply];
620 + });
621 +
622 + const res = await client.leaveRoom(ROOM_ID, {
623 + accessToken: CUSTOM_ACCESS_TOKEN,
624 + });
625 +
626 + expect(res).toEqual(reply);
627 + });
628 + });
629 +});
1 +import MockAdapter from 'axios-mock-adapter';
2 +
3 +import LineClient from '../LineClient';
4 +
5 +const RECIPIENT_ID = '1QAZ2WSX';
6 +const REPLY_TOKEN = 'nHuyWiB7yP5Zw52FIkcQobQuGDXCTA';
7 +const CUSTOM_ACCESS_TOKEN = '555555555';
8 +const ACCESS_TOKEN = '1234567890';
9 +const CHANNEL_SECRET = 'so-secret';
10 +
11 +const createMock = ({ customAccessToken } = {}) => {
12 + const client = new LineClient(ACCESS_TOKEN, CHANNEL_SECRET);
13 + const mock = new MockAdapter(client.axios);
14 + const headers = {
15 + Accept: 'application/json, text/plain, */*',
16 + 'Content-Type': 'application/json',
17 + Authorization: `Bearer ${customAccessToken || ACCESS_TOKEN}`,
18 + };
19 + return { client, mock, headers };
20 +};
21 +
22 +describe('Content', () => {
23 + describe('#retrieveMessageContent', () => {
24 + it('should call retrieveMessageContent api', async () => {
25 + const { client, mock } = createMock();
26 +
27 + const reply = Buffer.from('a content buffer');
28 +
29 + const MESSAGE_ID = '1234567890';
30 +
31 + mock.onGet(`/v2/bot/message/${MESSAGE_ID}/content`).reply(200, reply);
32 +
33 + const res = await client.retrieveMessageContent(MESSAGE_ID);
34 +
35 + expect(res).toEqual(reply);
36 + });
37 + });
38 +});
39 +
40 +describe('Profile', () => {
41 + describe('#getUserProfile', () => {
42 + it('should response user profile', async () => {
43 + expect.assertions(4);
44 +
45 + const { client, mock, headers } = createMock();
46 + const reply = {
47 + displayName: 'LINE taro',
48 + userId: RECIPIENT_ID,
49 + pictureUrl: 'http://obs.line-apps.com/...',
50 + statusMessage: 'Hello, LINE!',
51 + };
52 +
53 + mock.onGet().reply(config => {
54 + expect(config.url).toEqual(
55 + `https://api.line.me/v2/bot/profile/${RECIPIENT_ID}`
56 + );
57 + expect(config.data).toEqual(undefined);
58 + expect(config.headers).toEqual(headers);
59 + return [200, reply];
60 + });
61 +
62 + const res = await client.getUserProfile(RECIPIENT_ID);
63 +
64 + expect(res).toEqual(reply);
65 + });
66 +
67 + it('should work with custom access token', async () => {
68 + expect.assertions(4);
69 +
70 + const { client, mock, headers } = createMock({
71 + customAccessToken: CUSTOM_ACCESS_TOKEN,
72 + });
73 + const reply = {
74 + displayName: 'LINE taro',
75 + userId: RECIPIENT_ID,
76 + pictureUrl: 'http://obs.line-apps.com/...',
77 + statusMessage: 'Hello, LINE!',
78 + };
79 +
80 + mock.onGet().reply(config => {
81 + expect(config.url).toEqual(
82 + `https://api.line.me/v2/bot/profile/${RECIPIENT_ID}`
83 + );
84 + expect(config.data).toEqual(undefined);
85 + expect(config.headers).toEqual(headers);
86 + return [200, reply];
87 + });
88 +
89 + const res = await client.getUserProfile(RECIPIENT_ID, {
90 + accessToken: CUSTOM_ACCESS_TOKEN,
91 + });
92 +
93 + expect(res).toEqual(reply);
94 + });
95 +
96 + it('should return null when no user found', async () => {
97 + const { client, mock } = createMock();
98 +
99 + mock.onGet().reply(404, {
100 + message: 'Not found',
101 + });
102 +
103 + const res = await client.getUserProfile(RECIPIENT_ID);
104 +
105 + expect(res).toEqual(null);
106 + });
107 + });
108 +});
109 +
110 +describe('Account link', () => {
111 + describe('#issueLinkToken', () => {
112 + it('should response data with link token', async () => {
113 + expect.assertions(4);
114 +
115 + const { client, mock, headers } = createMock();
116 + const reply = {
117 + linkToken: 'NMZTNuVrPTqlr2IF8Bnymkb7rXfYv5EY',
118 + };
119 +
120 + mock.onPost().reply(config => {
121 + expect(config.url).toEqual(
122 + `https://api.line.me/v2/bot/user/${RECIPIENT_ID}/linkToken`
123 + );
124 + expect(config.data).toEqual(null);
125 + expect(config.headers).toEqual(headers);
126 + return [200, reply];
127 + });
128 +
129 + const res = await client.issueLinkToken(RECIPIENT_ID);
130 +
131 + expect(res).toEqual(reply);
132 + });
133 +
134 + it('should work with custom access token', async () => {
135 + expect.assertions(4);
136 +
137 + const { client, mock, headers } = createMock({
138 + customAccessToken: CUSTOM_ACCESS_TOKEN,
139 + });
140 + const reply = {
141 + linkToken: 'NMZTNuVrPTqlr2IF8Bnymkb7rXfYv5EY',
142 + };
143 +
144 + mock.onPost().reply(config => {
145 + expect(config.url).toEqual(
146 + `https://api.line.me/v2/bot/user/${RECIPIENT_ID}/linkToken`
147 + );
148 + expect(config.data).toEqual(null);
149 + expect(config.headers).toEqual(headers);
150 + return [200, reply];
151 + });
152 +
153 + const res = await client.issueLinkToken(RECIPIENT_ID, {
154 + accessToken: CUSTOM_ACCESS_TOKEN,
155 + });
156 +
157 + expect(res).toEqual(reply);
158 + });
159 + });
160 +});
161 +
162 +describe('Error', () => {
163 + it('should format correctly when no details', async () => {
164 + const { client, mock } = createMock();
165 +
166 + const reply = {
167 + message: 'The request body has 2 error(s)',
168 + };
169 +
170 + mock.onAny().reply(400, reply);
171 +
172 + let error;
173 + try {
174 + await client.replyText(REPLY_TOKEN, 'Hello!');
175 + } catch (err) {
176 + error = err;
177 + }
178 +
179 + expect(error.message).toEqual('LINE API - The request body has 2 error(s)');
180 + });
181 +
182 + it('should format correctly when details exist', async () => {
183 + const { client, mock } = createMock();
184 +
185 + const reply = {
186 + message: 'The request body has 2 error(s)',
187 + details: [
188 + { message: 'May not be empty', property: 'messages[0].text' },
189 + {
190 + message:
191 + 'Must be one of the following values: [text, image, video, audio, location, sticker, template, imagemap]',
192 + property: 'messages[1].type',
193 + },
194 + ],
195 + };
196 +
197 + mock.onAny().reply(400, reply);
198 +
199 + let error;
200 + try {
201 + await client.replyText(REPLY_TOKEN, 'Hello!');
202 + } catch (err) {
203 + error = err;
204 + }
205 +
206 + expect(error.message).toEqual(`LINE API - The request body has 2 error(s)
207 +- messages[0].text: May not be empty
208 +- messages[1].type: Must be one of the following values: [text, image, video, audio, location, sticker, template, imagemap]`);
209 + });
210 +});
1 +import MockAdapter from 'axios-mock-adapter';
2 +
3 +import LinePay from '../LinePay';
4 +
5 +const CHANNEL_ID = '1234567890';
6 +const CHANNEL_SECRET = 'so-secret';
7 +
8 +const createMock = () => {
9 + const client = new LinePay({
10 + channelId: CHANNEL_ID,
11 + channelSecret: CHANNEL_SECRET,
12 + });
13 + const mock = new MockAdapter(client.axios);
14 + return { client, mock };
15 +};
16 +
17 +describe('connect', () => {
18 + let axios;
19 + let _create;
20 + beforeEach(() => {
21 + axios = require('axios'); // eslint-disable-line global-require
22 + _create = axios.create;
23 + });
24 +
25 + afterEach(() => {
26 + axios.create = _create;
27 + });
28 +
29 + it('create axios with LINE PAY API', () => {
30 + axios.create = jest.fn();
31 + LinePay.connect({
32 + channelId: CHANNEL_ID,
33 + channelSecret: CHANNEL_SECRET,
34 + });
35 +
36 + expect(axios.create).toBeCalledWith({
37 + baseURL: 'https://api-pay.line.me/v2/',
38 + headers: {
39 + 'Content-Type': 'application/json',
40 + 'X-LINE-ChannelId': CHANNEL_ID,
41 + 'X-LINE-ChannelSecret': CHANNEL_SECRET,
42 + },
43 + });
44 + });
45 +});
46 +
47 +describe('constructor', () => {
48 + let axios;
49 + let _create;
50 + beforeEach(() => {
51 + axios = require('axios'); // eslint-disable-line global-require
52 + _create = axios.create;
53 + });
54 +
55 + afterEach(() => {
56 + axios.create = _create;
57 + });
58 +
59 + it('create axios with LINE PAY API', () => {
60 + axios.create = jest.fn();
61 + // eslint-disable-next-line no-new
62 + new LinePay({
63 + channelId: CHANNEL_ID,
64 + channelSecret: CHANNEL_SECRET,
65 + });
66 +
67 + expect(axios.create).toBeCalledWith({
68 + baseURL: 'https://api-pay.line.me/v2/',
69 + headers: {
70 + 'Content-Type': 'application/json',
71 + 'X-LINE-ChannelId': CHANNEL_ID,
72 + 'X-LINE-ChannelSecret': CHANNEL_SECRET,
73 + },
74 + });
75 + });
76 +
77 + it('support sandbox', () => {
78 + axios.create = jest.fn();
79 + // eslint-disable-next-line no-new
80 + new LinePay({
81 + channelId: CHANNEL_ID,
82 + channelSecret: CHANNEL_SECRET,
83 + sandbox: true,
84 + });
85 +
86 + expect(axios.create).toBeCalledWith({
87 + baseURL: 'https://sandbox-api-pay.line.me/v2/',
88 + headers: {
89 + 'Content-Type': 'application/json',
90 + 'X-LINE-ChannelId': CHANNEL_ID,
91 + 'X-LINE-ChannelSecret': CHANNEL_SECRET,
92 + },
93 + });
94 + });
95 +
96 + it('support origin', () => {
97 + axios.create = jest.fn();
98 + // eslint-disable-next-line no-new
99 + new LinePay({
100 + channelId: CHANNEL_ID,
101 + channelSecret: CHANNEL_SECRET,
102 + origin: 'https://mydummytestserver.com',
103 + });
104 +
105 + expect(axios.create).toBeCalledWith({
106 + baseURL: 'https://mydummytestserver.com/v2/',
107 + headers: {
108 + 'Content-Type': 'application/json',
109 + 'X-LINE-ChannelId': CHANNEL_ID,
110 + 'X-LINE-ChannelSecret': CHANNEL_SECRET,
111 + },
112 + });
113 + });
114 +});
115 +
116 +describe('#axios', () => {
117 + it('should return underlying http client', () => {
118 + const client = new LinePay({
119 + channelId: CHANNEL_ID,
120 + channelSecret: CHANNEL_SECRET,
121 + });
122 + expect(client.axios.get).toBeDefined();
123 + expect(client.axios.post).toBeDefined();
124 + expect(client.axios.put).toBeDefined();
125 + expect(client.axios.delete).toBeDefined();
126 + });
127 +});
128 +
129 +describe('#getPayments', () => {
130 + const reply = {
131 + returnCode: '0000',
132 + returnMessage: 'success',
133 + info: [
134 + {
135 + transactionId: 1020140728100001997,
136 + transactionDate: '2014-07-28T09:48:43Z',
137 + transactionType: 'PARTIAL_REFUND',
138 + amount: -5,
139 + productName: '',
140 + currency: 'USD',
141 + orderId: '20140101123123123',
142 + originalTransactionId: 1020140728100001999,
143 + },
144 + ],
145 + };
146 +
147 + it('should work with transactionId and orderId', async () => {
148 + const { client, mock } = createMock();
149 +
150 + mock
151 + .onGet('/payments?transactionId=20140101123123123&orderId=1002045572')
152 + .reply(200, reply);
153 +
154 + const result = await client.getPayments({
155 + transactionId: '20140101123123123',
156 + orderId: '1002045572',
157 + });
158 +
159 + expect(result).toEqual(reply.info);
160 + });
161 +
162 + it('should work with only transactionId', async () => {
163 + const { client, mock } = createMock();
164 +
165 + mock.onGet('/payments?transactionId=20140101123123123').reply(200, reply);
166 +
167 + const result = await client.getPayments({
168 + transactionId: '20140101123123123',
169 + });
170 +
171 + expect(result).toEqual(reply.info);
172 + });
173 +
174 + it('should work with only orderId', async () => {
175 + const { client, mock } = createMock();
176 +
177 + mock.onGet('/payments?orderId=1002045572').reply(200, reply);
178 +
179 + const result = await client.getPayments({
180 + orderId: '1002045572',
181 + });
182 +
183 + expect(result).toEqual(reply.info);
184 + });
185 +
186 + it('should throw without any id', async () => {
187 + const { client } = createMock();
188 +
189 + await expect(() => client.getPayments()).toThrow(
190 + /One of `transactionId` or `orderId` must be provided/
191 + );
192 + });
193 +
194 + it('should throw when not success', async () => {
195 + const { client, mock } = createMock();
196 +
197 + mock.onGet('/payments?orderId=1002045572').reply(200, {
198 + returnCode: '1104',
199 + returnMessage: 'merchant not found',
200 + });
201 +
202 + return expect(
203 + client.getPayments({
204 + orderId: '1002045572',
205 + })
206 + ).rejects.toThrow('LINE PAY API - 1104 merchant not found');
207 + });
208 +});
209 +
210 +describe('#getAuthorizations', () => {
211 + const reply = {
212 + returnCode: '0000',
213 + returnMessage: 'success',
214 + info: [
215 + {
216 + transactionId: 201612312312333401,
217 + transactionDate: '2014-07-28T09:48:43Z',
218 + transactionType: 'PAYMENT',
219 + payInfo: [
220 + {
221 + method: 'BALANCE',
222 + amount: 10,
223 + },
224 + {
225 + method: 'DISCOUNT',
226 + amount: 10,
227 + },
228 + ],
229 +
230 + productName: 'tes production',
231 + currency: 'USD',
232 + orderId: '20140101123123123',
233 + payStatus: 'AUTHORIZATION',
234 + authorizationExpireDate: '2014-07-28T09:48:43Z',
235 + },
236 + ],
237 + };
238 +
239 + it('should work with transactionId and orderId', async () => {
240 + const { client, mock } = createMock();
241 +
242 + mock
243 + .onGet(
244 + '/payments/authorizations?transactionId=20140101123123123&orderId=1002045572'
245 + )
246 + .reply(200, reply);
247 +
248 + const result = await client.getAuthorizations({
249 + transactionId: '20140101123123123',
250 + orderId: '1002045572',
251 + });
252 +
253 + expect(result).toEqual(reply.info);
254 + });
255 +
256 + it('should work with only transactionId', async () => {
257 + const { client, mock } = createMock();
258 +
259 + mock
260 + .onGet('/payments/authorizations?transactionId=20140101123123123')
261 + .reply(200, reply);
262 +
263 + const result = await client.getAuthorizations({
264 + transactionId: '20140101123123123',
265 + });
266 +
267 + expect(result).toEqual(reply.info);
268 + });
269 +
270 + it('should work with only orderId', async () => {
271 + const { client, mock } = createMock();
272 +
273 + mock.onGet('/payments/authorizations?orderId=1002045572').reply(200, reply);
274 +
275 + const result = await client.getAuthorizations({
276 + orderId: '1002045572',
277 + });
278 +
279 + expect(result).toEqual(reply.info);
280 + });
281 +
282 + it('should throw without any id', async () => {
283 + const { client } = createMock();
284 +
285 + await expect(() => client.getAuthorizations()).toThrow(
286 + /One of `transactionId` or `orderId` must be provided/
287 + );
288 + });
289 +
290 + it('should throw when not success', async () => {
291 + const { client, mock } = createMock();
292 +
293 + mock.onGet('/payments/authorizations?orderId=1002045572').reply(200, {
294 + returnCode: '1104',
295 + returnMessage: 'merchant not found',
296 + });
297 +
298 + return expect(
299 + client.getAuthorizations({
300 + orderId: '1002045572',
301 + })
302 + ).rejects.toThrow('LINE PAY API - 1104 merchant not found');
303 + });
304 +});
305 +
306 +describe('#reserve', () => {
307 + it('should call reserve api', async () => {
308 + const { client, mock } = createMock();
309 +
310 + const reply = {
311 + returnCode: '0000',
312 + returnMessage: 'OK',
313 + info: {
314 + transactionId: 123123123123,
315 + paymentUrl: {
316 + web: 'http://web-pay.line.me/web/wait?transactionReserveId=blahblah',
317 + app: 'line://pay/payment/blahblah',
318 + },
319 + paymentAccessToken: '187568751124',
320 + },
321 + };
322 +
323 + mock
324 + .onPost('/payments/request', {
325 + productName: 'test product',
326 + productImageUrl: 'http://testst.com',
327 + amount: 10,
328 + currency: 'USD',
329 + mid: 'os89dufgoiw8yer9021384rdfeq',
330 + orderId: '20140101123456789',
331 + confirmUrl:
332 + 'naversearchapp://inappbrowser?url=http%3A%2F%2FtestMall.com%2FcheckResult.nhn%3ForderId%3D20140101123456789',
333 + cancelUrl:
334 + 'naversearchapp://inappbrowser?url=http%3A%2F%2FtestMall.com%2ForderSheet.nhn%3ForderId%3D20140101123456789',
335 + capture: 'true',
336 + confirmUrlType: 'CLIENT',
337 + extras: {
338 + addFriends: [
339 + {
340 + type: 'LINE_AT',
341 + idList: ['@aaa', '@bbb'],
342 + },
343 + ],
344 + branchName: 'test_branch_1',
345 + },
346 + })
347 + .reply(200, reply);
348 +
349 + const result = await client.reserve({
350 + productName: 'test product',
351 + productImageUrl: 'http://testst.com',
352 + amount: 10,
353 + currency: 'USD',
354 + mid: 'os89dufgoiw8yer9021384rdfeq',
355 + orderId: '20140101123456789',
356 + confirmUrl:
357 + 'naversearchapp://inappbrowser?url=http%3A%2F%2FtestMall.com%2FcheckResult.nhn%3ForderId%3D20140101123456789',
358 + cancelUrl:
359 + 'naversearchapp://inappbrowser?url=http%3A%2F%2FtestMall.com%2ForderSheet.nhn%3ForderId%3D20140101123456789',
360 + capture: 'true',
361 + confirmUrlType: 'CLIENT',
362 + extras: {
363 + addFriends: [
364 + {
365 + type: 'LINE_AT',
366 + idList: ['@aaa', '@bbb'],
367 + },
368 + ],
369 + branchName: 'test_branch_1',
370 + },
371 + });
372 +
373 + expect(result).toEqual({
374 + transactionId: 123123123123,
375 + paymentUrl: {
376 + web: 'http://web-pay.line.me/web/wait?transactionReserveId=blahblah',
377 + app: 'line://pay/payment/blahblah',
378 + },
379 + paymentAccessToken: '187568751124',
380 + });
381 + });
382 +
383 + it('should throw when not success', async () => {
384 + const { client, mock } = createMock();
385 +
386 + const reply = {
387 + returnCode: '1104',
388 + returnMessage: 'merchant not found',
389 + info: {
390 + transactionId: 123123123123,
391 + paymentUrl: {
392 + web: 'http://web-pay.line.me/web/wait?transactionReserveId=blahblah',
393 + app: 'line://pay/payment/blahblah',
394 + },
395 + paymentAccessToken: '187568751124',
396 + },
397 + };
398 +
399 + mock
400 + .onPost('/payments/request', {
401 + productName: 'test product',
402 + productImageUrl: 'http://testst.com',
403 + amount: 10,
404 + currency: 'USD',
405 + mid: 'os89dufgoiw8yer9021384rdfeq',
406 + orderId: '20140101123456789',
407 + confirmUrl:
408 + 'naversearchapp://inappbrowser?url=http%3A%2F%2FtestMall.com%2FcheckResult.nhn%3ForderId%3D20140101123456789',
409 + cancelUrl:
410 + 'naversearchapp://inappbrowser?url=http%3A%2F%2FtestMall.com%2ForderSheet.nhn%3ForderId%3D20140101123456789',
411 + capture: 'true',
412 + confirmUrlType: 'CLIENT',
413 + extras: {
414 + addFriends: [
415 + {
416 + type: 'LINE_AT',
417 + idList: ['@aaa', '@bbb'],
418 + },
419 + ],
420 + branchName: 'test_branch_1',
421 + },
422 + })
423 + .reply(200, reply);
424 +
425 + return expect(
426 + client.reserve({
427 + productName: 'test product',
428 + productImageUrl: 'http://testst.com',
429 + amount: 10,
430 + currency: 'USD',
431 + mid: 'os89dufgoiw8yer9021384rdfeq',
432 + orderId: '20140101123456789',
433 + confirmUrl:
434 + 'naversearchapp://inappbrowser?url=http%3A%2F%2FtestMall.com%2FcheckResult.nhn%3ForderId%3D20140101123456789',
435 + cancelUrl:
436 + 'naversearchapp://inappbrowser?url=http%3A%2F%2FtestMall.com%2ForderSheet.nhn%3ForderId%3D20140101123456789',
437 + capture: 'true',
438 + confirmUrlType: 'CLIENT',
439 + extras: {
440 + addFriends: [
441 + {
442 + type: 'LINE_AT',
443 + idList: ['@aaa', '@bbb'],
444 + },
445 + ],
446 + branchName: 'test_branch_1',
447 + },
448 + })
449 + ).rejects.toThrow('LINE PAY API - 1104 merchant not found');
450 + });
451 +});
452 +
453 +describe('#confirm', () => {
454 + it('should call confirm api', async () => {
455 + const { client, mock } = createMock();
456 +
457 + const reply = {
458 + returnCode: '0000',
459 + returnMessage: 'OK',
460 + info: {
461 + orderId: 'order_210124213',
462 + transactionId: 20140101123123123,
463 + payInfo: [
464 + {
465 + method: 'BALANCE',
466 + amount: 10,
467 + },
468 + {
469 + method: 'DISCOUNT',
470 + amount: 10,
471 + },
472 + ],
473 + },
474 + };
475 +
476 + mock
477 + .onPost('/payments/sdhqiwouehrafdasrqoi123as/confirm', {
478 + amount: 1000,
479 + currency: 'TWD',
480 + })
481 + .reply(200, reply);
482 +
483 + const result = await client.confirm('sdhqiwouehrafdasrqoi123as', {
484 + amount: 1000,
485 + currency: 'TWD',
486 + });
487 +
488 + expect(result).toEqual({
489 + orderId: 'order_210124213',
490 + transactionId: 20140101123123123,
491 + payInfo: [
492 + {
493 + method: 'BALANCE',
494 + amount: 10,
495 + },
496 + {
497 + method: 'DISCOUNT',
498 + amount: 10,
499 + },
500 + ],
501 + });
502 + });
503 +
504 + it('should throw when not success', async () => {
505 + const { client, mock } = createMock();
506 +
507 + const reply = {
508 + returnCode: '1104',
509 + returnMessage: 'merchant not found',
510 + info: {
511 + orderId: 'order_210124213',
512 + transactionId: 20140101123123123,
513 + payInfo: [
514 + {
515 + method: 'BALANCE',
516 + amount: 10,
517 + },
518 + {
519 + method: 'DISCOUNT',
520 + amount: 10,
521 + },
522 + ],
523 + },
524 + };
525 +
526 + mock
527 + .onPost('/payments/sdhqiwouehrafdasrqoi123as/confirm', {
528 + amount: 1000,
529 + currency: 'TWD',
530 + })
531 + .reply(200, reply);
532 +
533 + return expect(
534 + client.confirm('sdhqiwouehrafdasrqoi123as', {
535 + amount: 1000,
536 + currency: 'TWD',
537 + })
538 + ).rejects.toThrow('LINE PAY API - 1104 merchant not found');
539 + });
540 +});
541 +
542 +describe('#capture', () => {
543 + it('should call capture api', async () => {
544 + const { client, mock } = createMock();
545 +
546 + const reply = {
547 + returnCode: '0000',
548 + returnMessage: 'OK',
549 + info: {
550 + transactionId: 20140101123123123,
551 + orderId: 'order_210124213',
552 + payInfo: [
553 + {
554 + method: 'BALANCE',
555 + amount: 10,
556 + },
557 + {
558 + method: 'DISCOUNT',
559 + amount: 10,
560 + },
561 + ],
562 + },
563 + };
564 +
565 + mock
566 + .onPost('/payments/authorizations/sdhqiwouehrafdasrqoi123as/capture', {
567 + amount: 1000,
568 + currency: 'TWD',
569 + })
570 + .reply(200, reply);
571 +
572 + const result = await client.capture('sdhqiwouehrafdasrqoi123as', {
573 + amount: 1000,
574 + currency: 'TWD',
575 + });
576 +
577 + expect(result).toEqual({
578 + transactionId: 20140101123123123,
579 + orderId: 'order_210124213',
580 + payInfo: [
581 + {
582 + method: 'BALANCE',
583 + amount: 10,
584 + },
585 + {
586 + method: 'DISCOUNT',
587 + amount: 10,
588 + },
589 + ],
590 + });
591 + });
592 +
593 + it('should throw when not success', async () => {
594 + const { client, mock } = createMock();
595 +
596 + const reply = {
597 + returnCode: '1104',
598 + returnMessage: 'merchant not found',
599 + info: {
600 + transactionId: 20140101123123123,
601 + orderId: 'order_210124213',
602 + payInfo: [
603 + {
604 + method: 'BALANCE',
605 + amount: 10,
606 + },
607 + {
608 + method: 'DISCOUNT',
609 + amount: 10,
610 + },
611 + ],
612 + },
613 + };
614 +
615 + mock
616 + .onPost('/payments/authorizations/sdhqiwouehrafdasrqoi123as/capture', {
617 + amount: 1000,
618 + currency: 'TWD',
619 + })
620 + .reply(200, reply);
621 +
622 + return expect(
623 + client.capture('sdhqiwouehrafdasrqoi123as', {
624 + amount: 1000,
625 + currency: 'TWD',
626 + })
627 + ).rejects.toThrow('LINE PAY API - 1104 merchant not found');
628 + });
629 +});
630 +
631 +describe('#void', () => {
632 + it('should call void api', async () => {
633 + const { client, mock } = createMock();
634 +
635 + const reply = {
636 + returnCode: '0000',
637 + returnMessage: 'OK',
638 + };
639 +
640 + mock
641 + .onPost('/payments/authorizations/sdhqiwouehrafdasrqoi123as/void')
642 + .reply(200, reply);
643 +
644 + const result = await client.void('sdhqiwouehrafdasrqoi123as');
645 +
646 + expect(result).toBeUndefined();
647 + });
648 +
649 + it('should throw when not success', async () => {
650 + const { client, mock } = createMock();
651 +
652 + const reply = {
653 + returnCode: '1104',
654 + returnMessage: 'merchant not found',
655 + };
656 +
657 + mock
658 + .onPost('/payments/authorizations/sdhqiwouehrafdasrqoi123as/void')
659 + .reply(200, reply);
660 +
661 + return expect(client.void('sdhqiwouehrafdasrqoi123as')).rejects.toThrow(
662 + 'LINE PAY API - 1104 merchant not found'
663 + );
664 + });
665 +});
666 +
667 +describe('#refund', () => {
668 + it('should call refund api', async () => {
669 + const { client, mock } = createMock();
670 +
671 + const reply = {
672 + returnCode: '0000',
673 + returnMessage: 'success',
674 + info: {
675 + refundTransactionId: 123123123123,
676 + refundTransactionDate: '2014-01-01T06:17:41Z',
677 + },
678 + };
679 +
680 + mock.onPost('/payments/sdhqiwouehrafdasrqoi123as/refund').reply(200, reply);
681 +
682 + const result = await client.refund('sdhqiwouehrafdasrqoi123as', {
683 + refundAmount: 500,
684 + });
685 +
686 + expect(result).toEqual({
687 + refundTransactionId: 123123123123,
688 + refundTransactionDate: '2014-01-01T06:17:41Z',
689 + });
690 + });
691 +
692 + it('should throw when not success', async () => {
693 + const { client, mock } = createMock();
694 +
695 + const reply = {
696 + returnCode: '1104',
697 + returnMessage: 'merchant not found',
698 + info: {
699 + refundTransactionId: 123123123123,
700 + refundTransactionDate: '2014-01-01T06:17:41Z',
701 + },
702 + };
703 +
704 + mock.onPost('/payments/sdhqiwouehrafdasrqoi123as/refund').reply(200, reply);
705 +
706 + return expect(
707 + client.refund('sdhqiwouehrafdasrqoi123as', {
708 + refundAmount: 500,
709 + })
710 + ).rejects.toThrow('LINE PAY API - 1104 merchant not found');
711 + });
712 +});
1 +import { Line } from '../browser';
2 +
3 +it('should export api correctly', () => {
4 + expect(Line).toBeDefined();
5 +});
1 +import { Line, LineClient, LinePay } from '..';
2 +
3 +it('should export api correctly', () => {
4 + expect(Line).toBeDefined();
5 + expect(LineClient).toBeDefined();
6 + expect(LinePay).toBeDefined();
7 +});
1 +/* @flow */
2 +
3 +import Line from './Line';
4 +
5 +export { Line };
6 +export default { Line };
1 +/* @flow */
2 +
3 +import Line from './Line';
4 +import LineClient from './LineClient';
5 +import LinePay from './LinePay';
6 +
7 +export { Line, LineClient, LinePay };
8 +export default { Line, LineClient, LinePay };
1 +language: node_js
2 +node_js:
3 + - "5"
4 + - "4"
5 + - "0.12"
...\ No newline at end of file ...\ No newline at end of file
1 +## 4.0.0 - 2018-02-02
2 +
3 + - Ignore empty string arguments and throw an exception for non-string. Closes #36, #18 ([da05242f381bfe1ae09d00b708cfdbdb93c1a85d](https://github.com/jfromaniello/url-join/commit/da05242f381bfe1ae09d00b708cfdbdb93c1a85d)), closes [#36](https://github.com/jfromaniello/url-join/issues/36) [#18](https://github.com/jfromaniello/url-join/issues/18)
4 +
5 +
6 +
7 +## 3.0.0 - 2018-01-12
8 +
9 + - add new test ([d65d7c1696cb53b53ceabadf1a77917196967b4c](https://github.com/jfromaniello/url-join/commit/d65d7c1696cb53b53ceabadf1a77917196967b4c))
10 + - Fixed to handle the colon in non-protocol separation role in the first part. ([9212db75f805031a9cc06120b5dd08a6cdd805e4](https://github.com/jfromaniello/url-join/commit/9212db75f805031a9cc06120b5dd08a6cdd805e4))
11 +
12 +
13 +
14 +## 2.0.5 - 2018-01-10
15 +
16 + - revert to previous behavior #30 ([b6943343af7bd723cbca266388e84e036543577d](https://github.com/jfromaniello/url-join/commit/b6943343af7bd723cbca266388e84e036543577d)), closes [#30](https://github.com/jfromaniello/url-join/issues/30)
17 +
18 +
19 +
20 +## 2.0.4 - 2018-01-10
21 +
22 + - fix bower.json ([9677895a4afe51d8a1d670980bc6fede71252e9a](https://github.com/jfromaniello/url-join/commit/9677895a4afe51d8a1d670980bc6fede71252e9a))
23 +
24 +
25 +
26 +## 2.0.3 - 2018-01-09
27 +
28 + - 2.0.3 ([7b7806b21cf81a3476e39ddb8a6f51272a276186](https://github.com/jfromaniello/url-join/commit/7b7806b21cf81a3476e39ddb8a6f51272a276186))
29 + - Added a test for simple paths for issue #21 ([be99b10a707b4d22aac015d19eb087fff46d4270](https://github.com/jfromaniello/url-join/commit/be99b10a707b4d22aac015d19eb087fff46d4270)), closes [#21](https://github.com/jfromaniello/url-join/issues/21)
30 + - Added some new tests for cases that fail. ([f1afbd62c3149476a9ef099ba523e85fb4839732](https://github.com/jfromaniello/url-join/commit/f1afbd62c3149476a9ef099ba523e85fb4839732))
31 + - Passes all the tests with these changes. ([8cde667f400fa83efc7ed5c2437c7cb25c7d7600](https://github.com/jfromaniello/url-join/commit/8cde667f400fa83efc7ed5c2437c7cb25c7d7600))
32 + - The protocol slashes should be normalized also when the protocol is not alone in the first argument. ([0ce1239c60f7bbb625d4ccbf1fcf044f37488bd8](https://github.com/jfromaniello/url-join/commit/0ce1239c60f7bbb625d4ccbf1fcf044f37488bd8))
33 +
34 +
35 +
36 +## 2.0.2 - 2017-05-18
37 +
38 + - fix: remove consecutives slashes ([33639364ef186e257b8424620017b9d1ba225539](https://github.com/jfromaniello/url-join/commit/33639364ef186e257b8424620017b9d1ba225539))
39 +
40 +
41 +
42 +## 2.0.1 - 2017-04-12
43 +
44 + - update mocha and bower.json ([ebd3665028b2408d405f9a31f8479e91c4ef52c1](https://github.com/jfromaniello/url-join/commit/ebd3665028b2408d405f9a31f8479e91c4ef52c1))
45 + - feat: add test ([46d3387141e5d2f751da699e02d57fc36bfe37a8](https://github.com/jfromaniello/url-join/commit/46d3387141e5d2f751da699e02d57fc36bfe37a8))
46 + - fix: ignore encoded url when removing consecusive slashes ([711add4e8af8fc97390adef14b9a4722cac5e70a](https://github.com/jfromaniello/url-join/commit/711add4e8af8fc97390adef14b9a4722cac5e70a))
47 +
48 +
49 +
50 +## 2.0.0 - 2017-04-11
51 +
52 + - Add a LICENSE file ([ffd3b2253470cee648152c55dd51c1bf4e688a60](https://github.com/jfromaniello/url-join/commit/ffd3b2253470cee648152c55dd51c1bf4e688a60))
53 + - change copyright year ([9f67671dd8ab23b4d2da6ae775efdf66d594eac3](https://github.com/jfromaniello/url-join/commit/9f67671dd8ab23b4d2da6ae775efdf66d594eac3))
54 + - refactor: use local startsWith function ([a1e1214644cd187f2584b79b4241ac3b8c9b9f1b](https://github.com/jfromaniello/url-join/commit/a1e1214644cd187f2584b79b4241ac3b8c9b9f1b))
55 + - fix: split logic for files ([d7053a99aa40b0c2f4802819f7e0643be8889ac4](https://github.com/jfromaniello/url-join/commit/d7053a99aa40b0c2f4802819f7e0643be8889ac4))
56 + - feat: add file protocol support ([48ebe0d84e8e2eca3a02fe5e3259cdd294e519dc](https://github.com/jfromaniello/url-join/commit/48ebe0d84e8e2eca3a02fe5e3259cdd294e519dc))
57 +
58 +
59 +
60 +## 1.1.0 - 2016-04-05
61 +
62 + - add .travis.yml ([c75e7507f72fd4be101b64bb44539fd249842cc0](https://github.com/jfromaniello/url-join/commit/c75e7507f72fd4be101b64bb44539fd249842cc0))
63 + - added new syntax to allow options, fixed #! urls ([b8e5d8372c55187cdd9c6fa5e02830f76858347e](https://github.com/jfromaniello/url-join/commit/b8e5d8372c55187cdd9c6fa5e02830f76858347e))
64 + - added travis, updated version in bower.json ([5a58405d89298e693e8f97a74b14324d83a8a87a](https://github.com/jfromaniello/url-join/commit/5a58405d89298e693e8f97a74b14324d83a8a87a))
65 + - fixed query string handling, closes #9, closes #4 ([e190fe28282287204dbe7877979f18b4570042f9](https://github.com/jfromaniello/url-join/commit/e190fe28282287204dbe7877979f18b4570042f9)), closes [#9](https://github.com/jfromaniello/url-join/issues/9) [#4](https://github.com/jfromaniello/url-join/issues/4)
66 +
67 +
68 +
69 +## 1.0.0 - 2016-03-23
70 +
71 +
72 +
73 +
74 +## 0.1.0 - 2016-03-23
75 +
76 + - 0.1.0 ([2db128d268dfd531f1af6c9bd0543458387e94cd](https://github.com/jfromaniello/url-join/commit/2db128d268dfd531f1af6c9bd0543458387e94cd))
77 + - add support for AMD and windows['url-join'] ([b02169596877a1e6cd518f1b0d711f38c721fb02](https://github.com/jfromaniello/url-join/commit/b02169596877a1e6cd518f1b0d711f38c721fb02))
78 + - added comments, fixed leading // ([3f72b6ea6fa84c4b254d0c656815a5df6b89a10a](https://github.com/jfromaniello/url-join/commit/3f72b6ea6fa84c4b254d0c656815a5df6b89a10a))
79 + - added test for leading // ([baac627b2052e1d9b5c05e48c8dc6a05a80e08fa](https://github.com/jfromaniello/url-join/commit/baac627b2052e1d9b5c05e48c8dc6a05a80e08fa))
80 + - bower init ([650dcfe72eee854108dd0832963553eae5ede7c5](https://github.com/jfromaniello/url-join/commit/650dcfe72eee854108dd0832963553eae5ede7c5))
81 + - initial ([af68a208966de3d4be757c9d0f4a918c6dfa360e](https://github.com/jfromaniello/url-join/commit/af68a208966de3d4be757c9d0f4a918c6dfa360e))
82 + - minor ([dde2dc6815f9a0476d7aade1d6848cbc5f3a14a4](https://github.com/jfromaniello/url-join/commit/dde2dc6815f9a0476d7aade1d6848cbc5f3a14a4))
83 + - minor ([4d9d8ee16591da2092739a172145f968f71598dc](https://github.com/jfromaniello/url-join/commit/4d9d8ee16591da2092739a172145f968f71598dc))
84 + - minor ([9ed0161497ee7d7d1b4b04d1735483a6216fe2c6](https://github.com/jfromaniello/url-join/commit/9ed0161497ee7d7d1b4b04d1735483a6216fe2c6))
85 + - simplify normalize function ([d6886a362828eacc028c6167b9ae0efd8b2fbfc8](https://github.com/jfromaniello/url-join/commit/d6886a362828eacc028c6167b9ae0efd8b2fbfc8))
86 +
87 +
88 +
1 +MIT License
2 +
3 +Copyright (c) 2015 José F. Romaniello
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.
1 +Join all arguments together and normalize the resulting url.
2 +
3 +## Install
4 +
5 +~~~
6 +npm install url-join
7 +~~~
8 +
9 +## Usage
10 +
11 +~~~javascript
12 +var urljoin = require('url-join');
13 +
14 +var fullUrl = urljoin('http://www.google.com', 'a', '/b/cd', '?foo=123');
15 +
16 +console.log(fullUrl);
17 +
18 +~~~
19 +
20 +Prints:
21 +
22 +~~~
23 +'http://www.google.com/a/b/cd?foo=123'
24 +~~~
25 +
26 +## Browser and AMD
27 +
28 +It also works in the browser, you can either include ```lib/url-join.js``` in your page:
29 +
30 +~~~html
31 +<script src="url-join.js"></script>
32 +<script type="text/javascript">
33 + urljoin('http://blabla.com', 'foo?a=1')
34 +</script>
35 +~~~
36 +
37 +Or using an AMD module system like requirejs:
38 +
39 +~~~javascript
40 +define(['path/url-join.js'], function (urljoin) {
41 + urljoin('http://blabla.com', 'foo?a=1');
42 +});
43 +~~~
44 +
45 +## License
46 +
47 +MIT
1 +#!/usr/bin/env node
2 +
3 +var changelog = require('conventional-changelog');
4 +var semver_regex = /\bv?(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?\b/ig;
5 +
6 +const commitPartial = ` - {{header}}
7 +
8 +{{~!-- commit hash --}} {{#if @root.linkReferences}}([{{hash}}]({{#if @root.host}}{{@root.host}}/{{/if}}{{#if @root.owner}}{{@root.owner}}/{{/if}}{{@root.repository}}/{{@root.commit}}/{{hash}})){{else}}{{hash~}}{{/if}}
9 +
10 +{{~!-- commit references --}}{{#if references}}, closes{{~#each references}} {{#if @root.linkReferences}}[{{#if this.owner}}{{this.owner}}/{{/if}}{{this.repository}}#{{this.issue}}]({{#if @root.host}}{{@root.host}}/{{/if}}{{#if this.repository}}{{#if this.owner}}{{this.owner}}/{{/if}}{{this.repository}}{{else}}{{#if @root.owner}}{{@root.owner}}/{{/if}}{{@root.repository}}{{/if}}/{{@root.issue}}/{{this.issue}}){{else}}{{#if this.owner}}{{this.owner}}/{{/if}}{{this.repository}}#{{this.issue}}{{/if}}{{/each}}{{/if}}
11 +`;
12 +
13 +const headerPartial = `## {{version}}{{#if title}} "{{title}}"{{/if}}{{#if date}} - {{date}}{{/if}}
14 +`;
15 +
16 +changelog({
17 + releaseCount: 19,
18 + // preset: 'jshint'
19 +}, null, null, null, {
20 + transform: function (commit) {
21 + if (commit.header && semver_regex.exec(commit.header)) {
22 + return null;
23 + }
24 + return commit;
25 + },
26 + commitPartial: commitPartial,
27 + headerPartial: headerPartial
28 +}).pipe(process.stdout);
1 +(function (name, context, definition) {
2 + if (typeof module !== 'undefined' && module.exports) module.exports = definition();
3 + else if (typeof define === 'function' && define.amd) define(definition);
4 + else context[name] = definition();
5 +})('urljoin', this, function () {
6 +
7 + function normalize (strArray) {
8 + var resultArray = [];
9 + if (strArray.length === 0) { return ''; }
10 +
11 + if (typeof strArray[0] !== 'string') {
12 + throw new TypeError('Url must be a string. Received ' + strArray[0]);
13 + }
14 +
15 + // If the first part is a plain protocol, we combine it with the next part.
16 + if (strArray[0].match(/^[^/:]+:\/*$/) && strArray.length > 1) {
17 + var first = strArray.shift();
18 + strArray[0] = first + strArray[0];
19 + }
20 +
21 + // There must be two or three slashes in the file protocol, two slashes in anything else.
22 + if (strArray[0].match(/^file:\/\/\//)) {
23 + strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, '$1:///');
24 + } else {
25 + strArray[0] = strArray[0].replace(/^([^/:]+):\/*/, '$1://');
26 + }
27 +
28 + for (var i = 0; i < strArray.length; i++) {
29 + var component = strArray[i];
30 +
31 + if (typeof component !== 'string') {
32 + throw new TypeError('Url must be a string. Received ' + component);
33 + }
34 +
35 + if (component === '') { continue; }
36 +
37 + if (i > 0) {
38 + // Removing the starting slashes for each component but the first.
39 + component = component.replace(/^[\/]+/, '');
40 + }
41 + if (i < strArray.length - 1) {
42 + // Removing the ending slashes for each component but the last.
43 + component = component.replace(/[\/]+$/, '');
44 + } else {
45 + // For the last component we will combine multiple slashes to a single one.
46 + component = component.replace(/[\/]+$/, '/');
47 + }
48 +
49 + resultArray.push(component);
50 +
51 + }
52 +
53 + var str = resultArray.join('/');
54 + // Each input component is now separated by a single slash except the possible first plain protocol part.
55 +
56 + // remove trailing slash before parameters or hash
57 + str = str.replace(/\/(\?|&|#[^!])/g, '$1');
58 +
59 + // replace ? in parameters with &
60 + var parts = str.split('?');
61 + str = parts.shift() + (parts.length > 0 ? '?': '') + parts.join('&');
62 +
63 + return str;
64 + }
65 +
66 + return function () {
67 + var input;
68 +
69 + if (typeof arguments[0] === 'object') {
70 + input = arguments[0];
71 + } else {
72 + input = [].slice.call(arguments);
73 + }
74 +
75 + return normalize(input);
76 + };
77 +
78 +});
1 +{
2 + "_from": "url-join@^4.0.1",
3 + "_id": "url-join@4.0.1",
4 + "_inBundle": false,
5 + "_integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
6 + "_location": "/url-join",
7 + "_phantomChildren": {},
8 + "_requested": {
9 + "type": "range",
10 + "registry": true,
11 + "raw": "url-join@^4.0.1",
12 + "name": "url-join",
13 + "escapedName": "url-join",
14 + "rawSpec": "^4.0.1",
15 + "saveSpec": null,
16 + "fetchSpec": "^4.0.1"
17 + },
18 + "_requiredBy": [
19 + "/messaging-api-line"
20 + ],
21 + "_resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
22 + "_shasum": "b642e21a2646808ffa178c4c5fda39844e12cde7",
23 + "_spec": "url-join@^4.0.1",
24 + "_where": "C:\\Users\\김수민\\Desktop\\ostt\\LINEBOT\\node_modules\\messaging-api-line",
25 + "author": {
26 + "name": "José F. Romaniello",
27 + "email": "jfromaniello@gmail.com",
28 + "url": "http://joseoncode.com"
29 + },
30 + "bugs": {
31 + "url": "https://github.com/jfromaniello/url-join/issues"
32 + },
33 + "bundleDependencies": false,
34 + "deprecated": false,
35 + "description": "Join urls and normalize as in path.join.",
36 + "devDependencies": {
37 + "conventional-changelog": "^1.1.10",
38 + "mocha": "^3.2.0",
39 + "should": "~1.2.1"
40 + },
41 + "homepage": "https://github.com/jfromaniello/url-join#readme",
42 + "keywords": [
43 + "url",
44 + "join"
45 + ],
46 + "license": "MIT",
47 + "main": "lib/url-join.js",
48 + "name": "url-join",
49 + "repository": {
50 + "type": "git",
51 + "url": "git://github.com/jfromaniello/url-join.git"
52 + },
53 + "scripts": {
54 + "test": "mocha --require should"
55 + },
56 + "version": "4.0.1"
57 +}
1 +var urljoin = require('../lib/url-join');
2 +var assert = require('assert');
3 +
4 +describe('url join', function () {
5 + it('should work for simple case', function () {
6 + urljoin('http://www.google.com/', 'foo/bar', '?test=123')
7 + .should.eql('http://www.google.com/foo/bar?test=123');
8 + });
9 +
10 + it('should work for simple case with new syntax', function () {
11 + urljoin(['http://www.google.com/', 'foo/bar', '?test=123'])
12 + .should.eql('http://www.google.com/foo/bar?test=123');
13 + });
14 +
15 + it('should work for hashbang urls', function () {
16 + urljoin(['http://www.google.com', '#!', 'foo/bar', '?test=123'])
17 + .should.eql('http://www.google.com/#!/foo/bar?test=123');
18 + });
19 +
20 + it('should be able to join protocol', function () {
21 + urljoin('http:', 'www.google.com/', 'foo/bar', '?test=123')
22 + .should.eql('http://www.google.com/foo/bar?test=123');
23 + });
24 +
25 + it('should be able to join protocol with slashes', function () {
26 + urljoin('http://', 'www.google.com/', 'foo/bar', '?test=123')
27 + .should.eql('http://www.google.com/foo/bar?test=123');
28 + });
29 +
30 + it('should remove extra slashes', function () {
31 + urljoin('http:', 'www.google.com///', 'foo/bar', '?test=123')
32 + .should.eql('http://www.google.com/foo/bar?test=123');
33 + });
34 +
35 + it('should not remove extra slashes in an encoded URL', function () {
36 + urljoin('http:', 'www.google.com///', 'foo/bar', '?url=http%3A//Ftest.com')
37 + .should.eql('http://www.google.com/foo/bar?url=http%3A//Ftest.com');
38 +
39 + urljoin('http://a.com/23d04b3/', '/b/c.html')
40 + .should.eql('http://a.com/23d04b3/b/c.html')
41 + .should.not.eql('http://a.com/23d04b3//b/c.html');
42 + });
43 +
44 + it('should support anchors in urls', function () {
45 + urljoin('http:', 'www.google.com///', 'foo/bar', '?test=123', '#faaaaa')
46 + .should.eql('http://www.google.com/foo/bar?test=123#faaaaa');
47 + });
48 +
49 + it('should support protocol-relative urls', function () {
50 + urljoin('//www.google.com', 'foo/bar', '?test=123')
51 + .should.eql('//www.google.com/foo/bar?test=123')
52 + });
53 +
54 + it('should support file protocol urls', function () {
55 + urljoin('file:/', 'android_asset', 'foo/bar')
56 + .should.eql('file://android_asset/foo/bar')
57 +
58 + urljoin('file:', '/android_asset', 'foo/bar')
59 + .should.eql('file://android_asset/foo/bar')
60 + });
61 +
62 + it('should support absolute file protocol urls', function () {
63 + urljoin('file:', '///android_asset', 'foo/bar')
64 + .should.eql('file:///android_asset/foo/bar')
65 +
66 + urljoin('file:///', 'android_asset', 'foo/bar')
67 + .should.eql('file:///android_asset/foo/bar')
68 +
69 + urljoin('file:///', '//android_asset', 'foo/bar')
70 + .should.eql('file:///android_asset/foo/bar')
71 +
72 + urljoin('file:///android_asset', 'foo/bar')
73 + .should.eql('file:///android_asset/foo/bar')
74 + });
75 +
76 + it('should merge multiple query params properly', function () {
77 + urljoin('http:', 'www.google.com///', 'foo/bar', '?test=123', '?key=456')
78 + .should.eql('http://www.google.com/foo/bar?test=123&key=456');
79 +
80 + urljoin('http:', 'www.google.com///', 'foo/bar', '?test=123', '?boom=value', '&key=456')
81 + .should.eql('http://www.google.com/foo/bar?test=123&boom=value&key=456');
82 +
83 + urljoin('http://example.org/x', '?a=1', '?b=2', '?c=3', '?d=4')
84 + .should.eql('http://example.org/x?a=1&b=2&c=3&d=4');
85 + });
86 +
87 + it('should merge slashes in paths correctly', function () {
88 + urljoin('http://example.org', 'a//', 'b//', 'A//', 'B//')
89 + .should.eql('http://example.org/a/b/A/B/');
90 + });
91 +
92 + it('should merge colons in paths correctly', function () {
93 + urljoin('http://example.org/', ':foo:', 'bar')
94 + .should.eql('http://example.org/:foo:/bar');
95 + });
96 +
97 + it('should merge just a simple path without URL correctly', function() {
98 + urljoin('/', 'test')
99 + .should.eql('/test');
100 + });
101 +
102 + it('should fail with segments that are not string', function() {
103 + assert.throws(() => urljoin(true),
104 + /Url must be a string. Received true/);
105 + assert.throws(() => urljoin('http://blabla.com/', 1),
106 + /Url must be a string. Received 1/);
107 + assert.throws(() => urljoin('http://blabla.com/', undefined, 'test'),
108 + /Url must be a string. Received undefined/);
109 + assert.throws(() => urljoin('http://blabla.com/', null, 'test'),
110 + /Url must be a string. Received null/);
111 + assert.throws(() => urljoin('http://blabla.com/', { foo: 123 }, 'test'),
112 + /Url must be a string. Received \[object Object\]/);
113 + });
114 +
115 + it('should merge a path with colon properly', function(){
116 + urljoin('/users/:userId', '/cars/:carId')
117 + .should.eql('/users/:userId/cars/:carId');
118 + });
119 +
120 + it('should merge slashes in protocol correctly', function () {
121 + urljoin('http://example.org', 'a')
122 + .should.eql('http://example.org/a');
123 + urljoin('http:', '//example.org', 'a')
124 + .should.eql('http://example.org/a');
125 + urljoin('http:///example.org', 'a')
126 + .should.eql('http://example.org/a');
127 + urljoin('file:///example.org', 'a')
128 + .should.eql('file:///example.org/a');
129 +
130 + urljoin('file:example.org', 'a')
131 + .should.eql('file://example.org/a');
132 +
133 + urljoin('file:/', 'example.org', 'a')
134 + .should.eql('file://example.org/a');
135 + urljoin('file:', '/example.org', 'a')
136 + .should.eql('file://example.org/a');
137 + urljoin('file:', '//example.org', 'a')
138 + .should.eql('file://example.org/a');
139 + });
140 +
141 + it('should skip empty strings', function() {
142 + urljoin('http://foobar.com', '', 'test')
143 + .should.eql('http://foobar.com/test');
144 + urljoin('', 'http://foobar.com', '', 'test')
145 + .should.eql('http://foobar.com/test');
146 + });
147 +
148 + it('should return an empty string if no arguments are supplied', function() {
149 + urljoin().should.eql('');
150 + });
151 +});
...@@ -230,6 +230,11 @@ ...@@ -230,6 +230,11 @@
230 "is-buffer": "^2.0.2" 230 "is-buffer": "^2.0.2"
231 } 231 }
232 }, 232 },
233 + "axios-error": {
234 + "version": "0.8.1",
235 + "resolved": "https://registry.npmjs.org/axios-error/-/axios-error-0.8.1.tgz",
236 + "integrity": "sha512-4YIf0FK2aO8HHVAQXvVFK2oPQSsOh3P1WQjkwQwO3oFjq4vO3S3YSyiRzyebBIR4NmcHaMJ6W5m0iHuisG7lTA=="
237 + },
233 "base64-js": { 238 "base64-js": {
234 "version": "1.3.1", 239 "version": "1.3.1",
235 "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", 240 "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
...@@ -778,11 +783,34 @@ ...@@ -778,11 +783,34 @@
778 "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 783 "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
779 "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 784 "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
780 }, 785 },
786 + "image-type": {
787 + "version": "4.1.0",
788 + "resolved": "https://registry.npmjs.org/image-type/-/image-type-4.1.0.tgz",
789 + "integrity": "sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg==",
790 + "requires": {
791 + "file-type": "^10.10.0"
792 + },
793 + "dependencies": {
794 + "file-type": {
795 + "version": "10.11.0",
796 + "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz",
797 + "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw=="
798 + }
799 + }
800 + },
781 "inherits": { 801 "inherits": {
782 "version": "2.0.3", 802 "version": "2.0.3",
783 "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 803 "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
784 "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 804 "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
785 }, 805 },
806 + "invariant": {
807 + "version": "2.2.4",
808 + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
809 + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
810 + "requires": {
811 + "loose-envify": "^1.0.0"
812 + }
813 + },
786 "ipaddr.js": { 814 "ipaddr.js": {
787 "version": "1.8.0", 815 "version": "1.8.0",
788 "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", 816 "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
...@@ -849,6 +877,11 @@ ...@@ -849,6 +877,11 @@
849 "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 877 "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
850 "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 878 "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
851 }, 879 },
880 + "js-tokens": {
881 + "version": "4.0.0",
882 + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
883 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
884 + },
852 "jsbn": { 885 "jsbn": {
853 "version": "0.1.1", 886 "version": "0.1.1",
854 "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 887 "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
...@@ -922,11 +955,24 @@ ...@@ -922,11 +955,24 @@
922 "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", 955 "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz",
923 "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" 956 "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI="
924 }, 957 },
958 + "lodash.omit": {
959 + "version": "4.5.0",
960 + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
961 + "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA="
962 + },
925 "long": { 963 "long": {
926 "version": "4.0.0", 964 "version": "4.0.0",
927 "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 965 "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
928 "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 966 "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
929 }, 967 },
968 + "loose-envify": {
969 + "version": "1.4.0",
970 + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
971 + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
972 + "requires": {
973 + "js-tokens": "^3.0.0 || ^4.0.0"
974 + }
975 + },
930 "lru-cache": { 976 "lru-cache": {
931 "version": "5.1.1", 977 "version": "5.1.1",
932 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 978 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
...@@ -945,6 +991,35 @@ ...@@ -945,6 +991,35 @@
945 "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 991 "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
946 "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 992 "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
947 }, 993 },
994 + "messaging-api-line": {
995 + "version": "0.8.3",
996 + "resolved": "https://registry.npmjs.org/messaging-api-line/-/messaging-api-line-0.8.3.tgz",
997 + "integrity": "sha512-EQRqW9UMtfQPw/PfnkFue7eSXZlxX2Ke5frFGhl6UHUW5Qfi4KPpvAqf2VTaU/cU7rGhGnZWuNbjoxkCWDyT8A==",
998 + "requires": {
999 + "axios": "^0.19.0",
1000 + "axios-error": "^0.8.1",
1001 + "debug": "^4.1.1",
1002 + "image-type": "^4.1.0",
1003 + "invariant": "^2.2.4",
1004 + "lodash.omit": "^4.5.0",
1005 + "url-join": "^4.0.1"
1006 + },
1007 + "dependencies": {
1008 + "debug": {
1009 + "version": "4.1.1",
1010 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
1011 + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
1012 + "requires": {
1013 + "ms": "^2.1.1"
1014 + }
1015 + },
1016 + "ms": {
1017 + "version": "2.1.2",
1018 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1019 + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
1020 + }
1021 + }
1022 + },
948 "methods": { 1023 "methods": {
949 "version": "1.1.2", 1024 "version": "1.1.2",
950 "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1025 "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
...@@ -1368,6 +1443,11 @@ ...@@ -1368,6 +1443,11 @@
1368 } 1443 }
1369 } 1444 }
1370 }, 1445 },
1446 + "url-join": {
1447 + "version": "4.0.1",
1448 + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
1449 + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
1450 + },
1371 "util-deprecate": { 1451 "util-deprecate": {
1372 "version": "1.0.2", 1452 "version": "1.0.2",
1373 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1453 "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 "aws-sdk": "^2.573.0", 15 "aws-sdk": "^2.573.0",
16 "dotenv": "^8.2.0", 16 "dotenv": "^8.2.0",
17 "express": "^4.16.4", 17 "express": "^4.16.4",
18 + "messaging-api-line": "^0.8.3",
18 "request": "^2.88.0" 19 "request": "^2.88.0"
19 } 20 }
20 } 21 }
......