node-red-contrib-line-messaging-api 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -35
- package/nodes/bloadcast/bloadcast.html +3 -2
- package/nodes/push/push.html +2 -1
- package/nodes/reply/reply.html +4 -3
- package/nodes/reply/reply.js +54 -22
- package/nodes/webhook/webhook.html +59 -0
- package/nodes/webhook/webhook.js +191 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -7,10 +7,7 @@ LINE Messagin APIを利用できるNode-REDのノードです。
|
|
|
7
7
|
以下のAPIを利用できます。
|
|
8
8
|
|
|
9
9
|
- LINE Messaging API
|
|
10
|
-
- Reply Message
|
|
11
|
-
- text
|
|
12
|
-
- image
|
|
13
|
-
- flex
|
|
10
|
+
- Webhook & Reply Message
|
|
14
11
|
- Push Message
|
|
15
12
|
- BloadCast Message
|
|
16
13
|
- LINE Notify
|
|
@@ -27,38 +24,13 @@ AdminタブからInstall
|
|
|
27
24
|
|
|
28
25
|
## 利用イメージ
|
|
29
26
|
|
|
30
|
-
### Reply Message
|
|
27
|
+
### Webhook & Reply Message
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
■テキスト例
|
|
39
|
-
|
|
40
|
-
```js
|
|
41
|
-
if (msg.payload.events[0].message.text == '夏') {
|
|
42
|
-
// 「夏」を受信したら「暑い」と返事する
|
|
43
|
-
msg.payload.events[0].message.text = '暑い';
|
|
44
|
-
} else {
|
|
45
|
-
// それ以外を受信したら「わかりません」と返事する
|
|
46
|
-
msg.payload.events[0].message.text = 'わかりません';
|
|
47
|
-
}
|
|
48
|
-
return msg;
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
■画像例
|
|
52
|
-
|
|
53
|
-
```js
|
|
54
|
-
msg.payload.events[0].message.originalContentUrl = `https://pbs.twimg.com/profile_images/1165566424699457537/IYBnJ1i5_400x400.jpg`
|
|
55
|
-
|
|
56
|
-
return msg;
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
- 3. Reply Messageノードでリプライ
|
|
60
|
-
|
|
61
|
-

|
|
29
|
+
1. Webhookノードを配置し、ダブルクリックで設定を開き、指定した `/path` と自身のホスト名の組み合わせ(Webhook URL)を、LINE Developersであらかじめ作成したMessaging APIに登録します。
|
|
30
|
+
2. ReplyMessageノードを配置し、チャネルのシークレットとアクセストークンを設定します。
|
|
31
|
+
3. WebhookノードとReplyMessageノードを接続してLINEにメッセージを送るとオウム返しBotができます。
|
|
32
|
+
[](https://gyazo.com/7da2dbecfc69515edf852cf7a26d9196)
|
|
33
|
+
4. WebhookノードとReplyMessageノードの中間で `msg.payload` をうまく作成すると様々なメッセージが送れます。文字列を指定すると通常のテキストメッセージに、[LINEで定義されているメッセージオブジェクト](https://developers.line.biz/ja/reference/messaging-api/#message-objects)を指定すればそのメッセージを返信することができます。
|
|
62
34
|
|
|
63
35
|
### Push Message
|
|
64
36
|
|
package/nodes/push/push.html
CHANGED
package/nodes/reply/reply.html
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
outputs:0,
|
|
15
15
|
icon: "comment.png",
|
|
16
16
|
align: "right",
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
paletteLabel: 'Reply',
|
|
18
|
+
label: function () {
|
|
19
|
+
return this.name || 'ReplyMessage';
|
|
19
20
|
}
|
|
20
21
|
});
|
|
21
22
|
</script>
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
|
|
39
40
|
<div class="form-row">
|
|
40
41
|
<label for="node-input-replyMessage"><i class="icon-tag"></i> ReplyMessage</label>
|
|
41
|
-
<input type="text" id="node-input-replyMessage" placeholder="
|
|
42
|
+
<input type="text" id="node-input-replyMessage" placeholder="static text message here">
|
|
42
43
|
</div>
|
|
43
44
|
</script>
|
|
44
45
|
|
package/nodes/reply/reply.js
CHANGED
|
@@ -16,13 +16,13 @@ module.exports = (RED) => {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
if(lineconfig.channelSecret === '' || lineconfig.channelAccessToken === '') {
|
|
20
|
-
this.error(RED._(
|
|
19
|
+
if (lineconfig.channelSecret === '' || lineconfig.channelAccessToken === '') {
|
|
20
|
+
this.error(RED._('token not found'));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const client = new line.Client(lineconfig);
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// 旧仕様
|
|
26
26
|
const handleEvent = (event) => {
|
|
27
27
|
if (event.type !== 'message') {
|
|
28
28
|
return Promise.resolve(null);
|
|
@@ -31,13 +31,13 @@ module.exports = (RED) => {
|
|
|
31
31
|
if (event.message.type === 'text') {
|
|
32
32
|
return client.replyMessage(event.replyToken, {
|
|
33
33
|
type: 'text',
|
|
34
|
-
text: config.replyMessage || event.message.text
|
|
35
|
-
|
|
34
|
+
text: config.replyMessage || event.message.text // 実際に返信の言葉を入れる箇所
|
|
35
|
+
});
|
|
36
36
|
} else if (event.message.type === 'flex') {
|
|
37
37
|
const message_text = event.message.altText;
|
|
38
38
|
|
|
39
39
|
return client.replyMessage(event.replyToken, {
|
|
40
|
-
type:
|
|
40
|
+
type: 'flex',
|
|
41
41
|
altText: message_text,
|
|
42
42
|
contents: event.message.text
|
|
43
43
|
});
|
|
@@ -52,27 +52,59 @@ module.exports = (RED) => {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
// 新仕様
|
|
56
|
+
const reply = (msg) => {
|
|
57
|
+
if (!msg.line || !msg.line.event || !msg.line.event.type) {
|
|
58
|
+
throw 'no valid LINE event found within msg';
|
|
59
|
+
} else if (!msg.line.event.replyToken) {
|
|
60
|
+
throw 'no replyable LINE event received';
|
|
61
|
+
} else if (!msg.payload) {
|
|
62
|
+
throw 'reply content (msg.payload) is empty';
|
|
63
|
+
} else if (typeof msg.payload === 'object' && msg.payload.type) {
|
|
64
|
+
// payloadがオブジェクトの場合はメッセージオブジェクト扱いで送信される
|
|
65
|
+
return client.replyMessage(msg.line.event.replyToken, msg.payload);
|
|
66
|
+
} else {
|
|
67
|
+
// payloadがそれ以外なら強制的に文字列に変換しテキストメッセージ扱いで送信する
|
|
68
|
+
return client.replyMessage(msg.line.event.replyToken, {
|
|
69
|
+
type: 'text',
|
|
70
|
+
text: config.replyMessage || String(msg.payload)
|
|
68
71
|
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
node.on('input', async (msg) => {
|
|
76
|
+
if (msg.line && msg.line.event) {
|
|
77
|
+
// 新仕様
|
|
78
|
+
try {
|
|
79
|
+
const result = await reply(msg);
|
|
80
|
+
console.info(result);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.warn(err);
|
|
83
|
+
node.error(err);
|
|
84
|
+
}
|
|
85
|
+
} else if (msg.payload.events) {
|
|
86
|
+
// 旧仕様
|
|
87
|
+
Promise
|
|
88
|
+
.all(msg.payload.events.map(handleEvent))
|
|
89
|
+
.then(result => {
|
|
90
|
+
// [{}]が返ってきてる
|
|
91
|
+
if (result.length === 1 && 0 === Object.keys(result[0]).length) {
|
|
92
|
+
result = { status: 200, message: 'Success' }
|
|
93
|
+
}
|
|
94
|
+
msg.payload = result;
|
|
95
|
+
node.send(msg)
|
|
96
|
+
}).catch(err => {
|
|
97
|
+
console.log(err);
|
|
98
|
+
node.error(err);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
69
101
|
});
|
|
70
102
|
}
|
|
71
103
|
|
|
72
|
-
RED.nodes.registerType(
|
|
104
|
+
RED.nodes.registerType('ReplyMessage', main, {
|
|
73
105
|
credentials: {
|
|
74
|
-
channelSecret: { type:
|
|
75
|
-
channelAccessToken: { type:
|
|
106
|
+
channelSecret: { type:'password' },
|
|
107
|
+
channelAccessToken: { type:'password' },
|
|
76
108
|
}
|
|
77
109
|
});
|
|
78
110
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('Webhook', {
|
|
3
|
+
category: 'LINE',
|
|
4
|
+
color: '#01B301',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
url: { value: '', required: true }
|
|
8
|
+
},
|
|
9
|
+
inputs: 0,
|
|
10
|
+
outputs: 1,
|
|
11
|
+
icon: 'inject.svg',
|
|
12
|
+
paletteLabel: 'Webhook',
|
|
13
|
+
label: function () {
|
|
14
|
+
if (this.name) {
|
|
15
|
+
return this.name;
|
|
16
|
+
} else if (this.url) {
|
|
17
|
+
let root = RED.settings.httpNodeRoot;
|
|
18
|
+
if (root.slice(-1) != '/') {
|
|
19
|
+
root = root + '/';
|
|
20
|
+
}
|
|
21
|
+
if (this.url.charAt(0) == '/') {
|
|
22
|
+
root += this.url.slice(1);
|
|
23
|
+
} else {
|
|
24
|
+
root += this.url;
|
|
25
|
+
}
|
|
26
|
+
return root;
|
|
27
|
+
} else {
|
|
28
|
+
return 'Webhook';
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
oneditprepare: function () {
|
|
32
|
+
let root = RED.settings.httpNodeRoot;
|
|
33
|
+
if (root.slice(-1) == '/') {
|
|
34
|
+
root = root.slice(0, -1);
|
|
35
|
+
}
|
|
36
|
+
if (root == '') {
|
|
37
|
+
$('#node-input-tip').hide();
|
|
38
|
+
} else {
|
|
39
|
+
$('#node-input-path').html(root);
|
|
40
|
+
$('#node-input-tip').show();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<script type="text/x-red" data-template-name="Webhook">
|
|
47
|
+
<div class="form-row">
|
|
48
|
+
<label for="node-input-url"><i class="fa fa-globe"></i> Path</label>
|
|
49
|
+
<input id="node-input-url" type="text" placeholder="/webhook">
|
|
50
|
+
</div>
|
|
51
|
+
<div class="form-row">
|
|
52
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
53
|
+
<input id="node-input-name" type="text" placeholder="Name">
|
|
54
|
+
</div>
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<script type="text/x-red" data-help-name="Webhook">
|
|
58
|
+
<p>A webhook endpoint node that receives from LINE platform when fire any events occurred.</p>
|
|
59
|
+
</script>
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 公式ノード http in ベース
|
|
3
|
+
* @see https://raw.githubusercontent.com/node-red/node-red/master/packages/node_modules/@node-red/nodes/core/network/21-httpin.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
module.exports = function (RED) {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const bodyParser = require('body-parser');
|
|
10
|
+
const cors = require('cors');
|
|
11
|
+
|
|
12
|
+
function createResponseWrapper(node, res) {
|
|
13
|
+
const wrapper = {
|
|
14
|
+
_res: res
|
|
15
|
+
};
|
|
16
|
+
const toWrap = [
|
|
17
|
+
'append',
|
|
18
|
+
'attachment',
|
|
19
|
+
'cookie',
|
|
20
|
+
'clearCookie',
|
|
21
|
+
'download',
|
|
22
|
+
'end',
|
|
23
|
+
'format',
|
|
24
|
+
'get',
|
|
25
|
+
'json',
|
|
26
|
+
'jsonp',
|
|
27
|
+
'links',
|
|
28
|
+
'location',
|
|
29
|
+
'redirect',
|
|
30
|
+
'render',
|
|
31
|
+
'send',
|
|
32
|
+
'sendfile',
|
|
33
|
+
'sendFile',
|
|
34
|
+
'sendStatus',
|
|
35
|
+
'set',
|
|
36
|
+
'status',
|
|
37
|
+
'type',
|
|
38
|
+
'vary'
|
|
39
|
+
];
|
|
40
|
+
toWrap.forEach(function (f) {
|
|
41
|
+
wrapper[f] = function () {
|
|
42
|
+
node.warn(RED._('webhook.errors.deprecated-call', { method: 'msg.res.' + f }));
|
|
43
|
+
const result = res[f].apply(res, arguments);
|
|
44
|
+
if (result === res) {
|
|
45
|
+
return wrapper;
|
|
46
|
+
} else {
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return wrapper;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let corsHandler = function (req, res, next) { next(); }
|
|
55
|
+
|
|
56
|
+
if (RED.settings.httpNodeCors) {
|
|
57
|
+
corsHandler = cors(RED.settings.httpNodeCors);
|
|
58
|
+
RED.httpNode.options('*', corsHandler);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// LINEイベント種別から適切な文字列を返す
|
|
62
|
+
function eventToString(event) {
|
|
63
|
+
if (event.type === 'message') {
|
|
64
|
+
// メッセージ系イベント
|
|
65
|
+
switch (event.message.type) {
|
|
66
|
+
case 'text':
|
|
67
|
+
return event.message.text;
|
|
68
|
+
case 'image':
|
|
69
|
+
case 'video':
|
|
70
|
+
case 'audio':
|
|
71
|
+
case 'file':
|
|
72
|
+
// 実装予定
|
|
73
|
+
return '(media)';
|
|
74
|
+
case 'location':
|
|
75
|
+
return event.message.address;
|
|
76
|
+
case 'sticker':
|
|
77
|
+
return '(sticker)';
|
|
78
|
+
default:
|
|
79
|
+
return '(unknown message event)';
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// メッセージじゃない
|
|
83
|
+
switch (event.type) {
|
|
84
|
+
case 'unsend':
|
|
85
|
+
return '(unsend)';
|
|
86
|
+
case 'follow':
|
|
87
|
+
return '(follow)';
|
|
88
|
+
case 'unfollow':
|
|
89
|
+
return '(unfollow)';
|
|
90
|
+
case 'join':
|
|
91
|
+
return '(join)';
|
|
92
|
+
case 'leave':
|
|
93
|
+
return '(leave)';
|
|
94
|
+
case 'memberJoined':
|
|
95
|
+
return '(memberJoined)';
|
|
96
|
+
case 'memberLeft':
|
|
97
|
+
return '(memberLeft)';
|
|
98
|
+
case 'postback':
|
|
99
|
+
if (event.postback.params.datetime) return event.postback.params.datetime;
|
|
100
|
+
else if (event.postback.params.newRichMenuAliasId) return event.postback.params.newRichMenuAliasId;
|
|
101
|
+
else '(postback)';
|
|
102
|
+
case 'videoPlayComplete':
|
|
103
|
+
return 'videoPlayComplete';
|
|
104
|
+
case 'beacon':
|
|
105
|
+
return (event.beacon.dm) ? event.beacon.dm : event.beacon.hwid;
|
|
106
|
+
case 'accountLink':
|
|
107
|
+
return '(accountLink)';
|
|
108
|
+
case 'things': // typeによって連携・連携解除・シナリオ実行の3種がある
|
|
109
|
+
return event.things.deviceId;
|
|
110
|
+
default:
|
|
111
|
+
return '(unknown event)';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function Webhook(config) {
|
|
117
|
+
RED.nodes.createNode(this, config);
|
|
118
|
+
if (RED.settings.httpNodeRoot !== false) {
|
|
119
|
+
|
|
120
|
+
if (!config.url) {
|
|
121
|
+
this.warn(RED._('webhook.errors.missing-path'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.url = config.url;
|
|
125
|
+
if (this.url[0] !== '/') {
|
|
126
|
+
this.url = '/' + this.url;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const node = this;
|
|
130
|
+
|
|
131
|
+
// エラー時
|
|
132
|
+
this.errorHandler = function (err, req, res, next) {
|
|
133
|
+
node.warn(err);
|
|
134
|
+
res.sendStatus(500);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// 受信してmsgを送り出すところ
|
|
138
|
+
this.callback = function (req, res) {
|
|
139
|
+
const msgid = RED.util.generateId();
|
|
140
|
+
res._msgid = msgid;
|
|
141
|
+
// Webhook共通
|
|
142
|
+
if (req.body.destination && req.body.events) {
|
|
143
|
+
// イベントと同数のmsgを送信
|
|
144
|
+
req.body.events.forEach(event => {
|
|
145
|
+
node.send({
|
|
146
|
+
_msgid: msgid,
|
|
147
|
+
req: req,
|
|
148
|
+
res: createResponseWrapper(node, res),
|
|
149
|
+
line: {
|
|
150
|
+
destination: req.body.destination,
|
|
151
|
+
event: event, // 個別のイベント
|
|
152
|
+
events: req.body.events, // 生イベント配列
|
|
153
|
+
},
|
|
154
|
+
payload: eventToString(event)
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// 空データをLINEプラットフォームへ返す (200 OK)
|
|
159
|
+
const wrapper = createResponseWrapper(node, res);
|
|
160
|
+
wrapper._res.set('content-length', 0);
|
|
161
|
+
wrapper._res.status(200).send('');
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const httpMiddleware = function (req, res, next) { next(); }
|
|
165
|
+
|
|
166
|
+
if (RED.settings.httpNodeMiddleware) {
|
|
167
|
+
if (typeof RED.settings.httpNodeMiddleware === 'function' || Array.isArray(RED.settings.httpNodeMiddleware)) {
|
|
168
|
+
httpMiddleware = RED.settings.httpNodeMiddleware;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const maxApiRequestSize = RED.settings.apiMaxLength || '5mb';
|
|
173
|
+
const jsonParser = bodyParser.json({ limit: maxApiRequestSize });
|
|
174
|
+
|
|
175
|
+
// 必ずPOSTで受ける
|
|
176
|
+
RED.httpNode.post(this.url, httpMiddleware, corsHandler, jsonParser, this.callback, this.errorHandler);
|
|
177
|
+
|
|
178
|
+
this.on('close', function () {
|
|
179
|
+
const node = this;
|
|
180
|
+
RED.httpNode._router.stack.forEach(function (route, i, routes) {
|
|
181
|
+
if (route.route && route.route.path === node.url && route.route.methods['post']) {
|
|
182
|
+
routes.splice(i, 1);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
} else {
|
|
187
|
+
this.warn(RED._('webhook.errors.not-created'));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
RED.nodes.registerType('Webhook', Webhook);
|
|
191
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-line-messaging-api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "LINE Messaging APIです",
|
|
5
5
|
"main": "line.js",
|
|
6
6
|
"repository": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"node-red": {
|
|
15
15
|
"nodes": {
|
|
16
|
+
"Webhook": "./nodes/webhook/webhook.js",
|
|
16
17
|
"ReplyMessage": "./nodes/reply/reply.js",
|
|
17
18
|
"PushMessage": "./nodes/push/push.js",
|
|
18
19
|
"BloadcastMessage": "./nodes/bloadcast/bloadcast.js",
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
"license": "Apache 2.0",
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@line/bot-sdk": "^7.2.0",
|
|
32
|
+
"body-parser": "^1.19.1",
|
|
33
|
+
"cors": "^2.8.5",
|
|
31
34
|
"express": "^4.17.1"
|
|
32
35
|
}
|
|
33
36
|
}
|