├── .gitignore
├── README.md
├── database.rules
├── firebase.json
├── firestore.rules
└── functions
├── .eslintrc.json
├── index.js
├── line.js
├── package.json
├── template.js
└── util.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | */npm-debug.log
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | firebase-debug.log*
9 | firebase-debug.*.log*
10 | **/firebase-debug.log
11 | **/ui-debug.log
12 | **/database-debug.log
13 | **/pubsub-debug.log
14 |
15 | # Diagnostic reports (https://nodejs.org/api/report.html)
16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
17 |
18 | # Firebase cache
19 | **/.firebase
20 |
21 | # Firebase config
22 | **/.firebaserc
23 |
24 | # Runtime data
25 | pids
26 | *.pid
27 | *.seed
28 | *.pid.lock
29 |
30 | # Directory for instrumented libs generated by jscoverage/JSCover
31 | lib-cov
32 |
33 | # Coverage directory used by tools like istanbul
34 | coverage
35 | *.lcov
36 |
37 | # nyc test coverage
38 | .nyc_output
39 |
40 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
41 | .grunt
42 |
43 | # Bower dependency directory (https://bower.io/)
44 | bower_components
45 |
46 | # node-waf configuration
47 | .lock-wscript
48 |
49 | # Compiled binary addons (http://nodejs.org/api/addons.html)
50 | build/Release
51 |
52 | # Dependency directories
53 | **/node_modules/*
54 | **/jspm_packages/*
55 |
56 | # TypeScript v1 declaration files
57 | typings/
58 |
59 | # TypeScript cache
60 | *.tsbuildinfo
61 |
62 | # Optional npm cache directory
63 | .npm
64 |
65 | # Optional eslint cache
66 | .eslintcache
67 |
68 | # Microbundle cache
69 | .rpt2_cache/
70 | .rts2_cache_cjs/
71 | .rts2_cache_es/
72 | .rts2_cache_umd/
73 |
74 | # Optional REPL history
75 | .node_repl_history
76 |
77 | # Output of 'npm pack'
78 | *.tgz
79 |
80 | # Yarn Integrity file
81 | .yarn-integrity
82 | yarn.lock
83 |
84 | # dotenv environment variables file
85 | .env
86 | .env.test
87 |
88 | # parcel-bundler cache (https://parceljs.org/)
89 | .cache
90 |
91 | # Next.js build output
92 | .next
93 |
94 | # Nuxt.js build / generate output
95 | .nuxt
96 | dist
97 |
98 | # Gatsby files
99 | .cache/
100 |
101 | # vuepress build output
102 | .vuepress/dist
103 |
104 | # Serverless directories
105 | .serverless/
106 |
107 | # FuseBox cache
108 | .fusebox/
109 |
110 | # DynamoDB Local files
111 | .dynamodb/
112 |
113 | # TernJS port file
114 | .tern-port
115 |
116 | .idea
117 | **/.runtimeconfig.json
118 | **/package-lock.json
119 | **/tsconfig-compile.json
120 | service-account.json
121 | service-account-credentials.json
122 |
123 | .DS_Store
124 | Thumbs.db
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LINE Bot 101 by Messaging API and Cloud Functions for Firebase
2 | Code sample for creating LINE Bot by Messaging API and Cloud Functions for Firebase for beginner
3 |
4 | ### What's Cloud Functions for Firebase?
5 |
6 | Cloud Functions is a hosted, private, and scalable Node.js environment where you can run JavaScript code. [Cloud Functions for Firebase](https://firebase.google.com/features/functions) integrates the Firebase platform by letting you write code that responds to events and invokes functionality exposed by other Firebase features.
7 |
8 | ## Prerequisites
9 | * [Create a channel on the LINE Developers console](https://developers.line.biz/en/docs/messaging-api/getting-started/)
10 | * To learn how to get started with Cloud Functions for Firebase by having a look at our [Getting Started Guide](https://firebase.google.com/docs/functions/get-started)
11 | * Select Blaze plan for your project in [Firebase Console](https://console.firebase.google.com/)
12 |
13 | ## Features
14 | * Reply Message
15 | * Push Message
16 | * Multicast Message
17 | * Broadcast Message
18 |
19 | ## Blog
20 | * [สร้าง LINE Bot ด้วย Messaging API และ Cloud Functions for Firebase](https://medium.com/linedevth/20d284edea1b)
21 |
22 | ## Screenshots
23 |
24 |
25 |  |
26 |  |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/database.rules:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | ".read": true,
4 | ".write": true
5 | }
6 | }
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": {
3 | "predeploy": [
4 | "npm --prefix \"$RESOURCE_DIR\" run lint"
5 | ],
6 | "source": "functions"
7 | },
8 | "database": {
9 | "rules": "database.rules"
10 | },
11 | "firestore": {
12 | "rules": "firestore.rules"
13 | },
14 | "emulators": {
15 | "functions": {
16 | "port": 5001
17 | },
18 | "firestore": {
19 | "port": 8080
20 | },
21 | "database": {
22 | "port": 9000
23 | },
24 | "ui": {
25 | "enabled": true
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/firestore.rules:
--------------------------------------------------------------------------------
1 | service cloud.firestore {
2 | match /databases/{database}/documents {
3 | match /{document=**} {
4 | allow read, write;
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/functions/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | // Required for certain syntax usages
4 | "ecmaVersion": 2017
5 | },
6 | "plugins": [
7 | "promise"
8 | ],
9 | "extends": "eslint:recommended",
10 | "rules": {
11 | // Removed rule "disallow the use of console" from recommended eslint rules
12 | "no-console": "off",
13 |
14 | // Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules
15 | "no-regex-spaces": "off",
16 |
17 | // Removed rule "disallow the use of debugger" from recommended eslint rules
18 | "no-debugger": "off",
19 |
20 | // Removed rule "disallow unused variables" from recommended eslint rules
21 | "no-unused-vars": "off",
22 |
23 | // Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules
24 | "no-mixed-spaces-and-tabs": "off",
25 |
26 | // Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules
27 | "no-undef": "off",
28 |
29 | // Warn against template literal placeholder syntax in regular strings
30 | "no-template-curly-in-string": 1,
31 |
32 | // Warn if return statements do not either always or never specify values
33 | "consistent-return": 1,
34 |
35 | // Warn if no return statements in callbacks of array methods
36 | "array-callback-return": 1,
37 |
38 | // Require the use of === and !==
39 | "eqeqeq": 2,
40 |
41 | // Disallow the use of alert, confirm, and prompt
42 | "no-alert": 2,
43 |
44 | // Disallow the use of arguments.caller or arguments.callee
45 | "no-caller": 2,
46 |
47 | // Disallow null comparisons without type-checking operators
48 | "no-eq-null": 2,
49 |
50 | // Disallow the use of eval()
51 | "no-eval": 2,
52 |
53 | // Warn against extending native types
54 | "no-extend-native": 1,
55 |
56 | // Warn against unnecessary calls to .bind()
57 | "no-extra-bind": 1,
58 |
59 | // Warn against unnecessary labels
60 | "no-extra-label": 1,
61 |
62 | // Disallow leading or trailing decimal points in numeric literals
63 | "no-floating-decimal": 2,
64 |
65 | // Warn against shorthand type conversions
66 | "no-implicit-coercion": 1,
67 |
68 | // Warn against function declarations and expressions inside loop statements
69 | "no-loop-func": 1,
70 |
71 | // Disallow new operators with the Function object
72 | "no-new-func": 2,
73 |
74 | // Warn against new operators with the String, Number, and Boolean objects
75 | "no-new-wrappers": 1,
76 |
77 | // Disallow throwing literals as exceptions
78 | "no-throw-literal": 2,
79 |
80 | // Require using Error objects as Promise rejection reasons
81 | "prefer-promise-reject-errors": 2,
82 |
83 | // Enforce “for” loop update clause moving the counter in the right direction
84 | "for-direction": 2,
85 |
86 | // Enforce return statements in getters
87 | "getter-return": 2,
88 |
89 | // Disallow await inside of loops
90 | "no-await-in-loop": 2,
91 |
92 | // Disallow comparing against -0
93 | "no-compare-neg-zero": 2,
94 |
95 | // Warn against catch clause parameters from shadowing variables in the outer scope
96 | "no-catch-shadow": 1,
97 |
98 | // Disallow identifiers from shadowing restricted names
99 | "no-shadow-restricted-names": 2,
100 |
101 | // Enforce return statements in callbacks of array methods
102 | "callback-return": 2,
103 |
104 | // Require error handling in callbacks
105 | "handle-callback-err": 2,
106 |
107 | // Warn against string concatenation with __dirname and __filename
108 | "no-path-concat": 1,
109 |
110 | // Prefer using arrow functions for callbacks
111 | "prefer-arrow-callback": 1,
112 |
113 | // Return inside each then() to create readable and reusable Promise chains.
114 | // Forces developers to return console logs and http calls in promises.
115 | "promise/always-return": 2,
116 |
117 | //Enforces the use of catch() on un-returned promises
118 | "promise/catch-or-return": 2,
119 |
120 | // Warn against nested then() or catch() statements
121 | "promise/no-nesting": 1
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/functions/index.js:
--------------------------------------------------------------------------------
1 | const functions = require("firebase-functions");
2 | const util = require("./util");
3 | const runtimeOpts = { timeoutSeconds: 8, memory: "1GB", minInstances: 1 };
4 | const region = "asia-northeast1";
5 |
6 | exports.LineProxy = functions.region(region).runWith(runtimeOpts).https.onRequest(async (req, res) => {
7 | if (req.method === "POST") {
8 | if (!util.verifySignature(req.headers["x-line-signature"], req.body)) {
9 | return res.status(401).send("Unauthorized");
10 | }
11 |
12 | let event = req.body.events[0];
13 | if (event === undefined) {
14 | return res.end();
15 | }
16 |
17 | if (event.type === "message") {
18 | if (event.message.type !== "text") {
19 | await util.reply(event.replyToken, { type: "text", text: JSON.stringify(event) });
20 | } else {
21 | await util.postToDialogflow(req);
22 | }
23 | }
24 | }
25 | return res.send(req.method);
26 | });
27 |
28 | const line = require('./line');
29 | exports.lineBotReply = line.botReply;
30 | exports.lineBotPush = line.botPush;
31 | exports.lineBotMulticast = line.botMulticast;
32 | exports.lineBotBroadcast = line.botBroadcast;
--------------------------------------------------------------------------------
/functions/line.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 |
3 | const functions = require("firebase-functions");
4 | const axios = require("axios");
5 | const util = require("./util");
6 |
7 | const runtimeOpts = { timeoutSeconds: 8, memory: "1GB", maxInstances: 1 };
8 | const region = "asia-northeast1";
9 | const LINE_USER_ID = functions.config().lineoa.jirawatee.userid;
10 | const OPENWEATHER_API = "https://api.openweathermap.org/data/2.5/weather/";
11 | const OPENWEATHER_APPID = functions.config().openweather.appid;
12 |
13 | exports.botReply = functions.region(region).runWith(runtimeOpts).https.onRequest((req, res) => {
14 | if (req.method === "POST") {
15 | util.reply(
16 | req.body.events[0].replyToken,
17 | { type: "text", text: JSON.stringify(req.body) }
18 | )
19 | }
20 | return res.status(200).send(req.method)
21 | });
22 |
23 | exports.botPush = functions.region(region).runWith(runtimeOpts).https.onRequest(async (req, res) => {
24 | const response = await axios({
25 | method: 'get',
26 | url: `${OPENWEATHER_API}?appid=${OPENWEATHER_APPID}&units=metric&type=accurate&zip=10330,th`
27 | })
28 | const msg = `City: ${response.data.name}\nWeather: ${response.data.weather[0].description}\nTemperature: ${response.data.main.temp}`
29 | util.push(LINE_USER_ID, { type: "text", text: msg })
30 | return res.status(200).send({ message: `Push: ${msg}` })
31 | });
32 |
33 | exports.botMulticast = functions.region(region).runWith(runtimeOpts).https.onRequest((req, res) => {
34 | const msg = req.query.msg
35 | if (msg !== undefined && msg.trim() !== "") {
36 | util.multicast({ type: "text", text: msg })
37 | return res.status(200).send({ message: `Multicast: ${msg}` })
38 | } else {
39 | return res.status(400).send({ message: "Text not found" })
40 | }
41 | });
42 |
43 | exports.botBroadcast = functions.region(region).runWith(runtimeOpts).https.onRequest((req, res) => {
44 | const msg = req.query.msg
45 | if (msg !== undefined && msg.trim() !== "") {
46 | util.broadcast({ type: "text", text: msg })
47 | return res.status(200).send({ message: `Broadcast: ${msg}` })
48 | } else {
49 | const ret = { message: "Text not found" }
50 | return res.status(400).send(ret)
51 | }
52 | });
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "description": "LINE Bot 101 by Messaging API and Cloud Functions for Firebase",
4 | "scripts": {
5 | "lint": "eslint .",
6 | "serve": "firebase emulators:start --only functions",
7 | "shell": "firebase functions:shell",
8 | "start": "npm run shell",
9 | "deploy": "firebase deploy --only functions",
10 | "logs": "firebase functions:log"
11 | },
12 | "dependencies": {
13 | "axios": "^0.21.1",
14 | "cheerio": "^1.0.0-rc.9",
15 | "firebase-admin": "^9.8.0",
16 | "firebase-functions": "^3.14.1",
17 | "uuid-v4": "^0.1.0"
18 | },
19 | "devDependencies": {
20 | "eslint": "^7.27.0",
21 | "eslint-plugin-promise": "^5.1.0"
22 | },
23 | "engines": {
24 | "node": "14"
25 | },
26 | "private": true
27 | }
28 |
--------------------------------------------------------------------------------
/functions/template.js:
--------------------------------------------------------------------------------
1 | const linepay = () => ({
2 | "type": "flex",
3 | "altText": "Flex Message",
4 | "contents": {
5 | "type": "bubble",
6 | "body": {
7 | "type": "box",
8 | "layout": "vertical",
9 | "spacing": "md",
10 | "contents": [
11 | {
12 | "type": "box",
13 | "layout": "vertical",
14 | "spacing": "sm",
15 | "contents": [
16 | {
17 | "type": "box",
18 | "layout": "baseline",
19 | "contents": [
20 | {
21 | "type": "text",
22 | "text": "Burger",
23 | "weight": "bold",
24 | "margin": "sm",
25 | "flex": 0
26 | },
27 | {
28 | "type": "text",
29 | "text": "1฿",
30 | "size": "sm",
31 | "align": "end",
32 | "color": "#aaaaaa"
33 | }
34 | ]
35 | }
36 | ]
37 | }
38 | ]
39 | },
40 | "footer": {
41 | "type": "box",
42 | "layout": "vertical",
43 | "contents": [
44 | {
45 | "type": "button",
46 | "style": "primary",
47 | "color": "#905c44",
48 | "action": {
49 | "type": 'postback',
50 | "label": "burger",
51 | "data": "item=burger"
52 | }
53 | }
54 | ]
55 | }
56 | }
57 | });
58 |
59 | const linehack = (data) => ({
60 | "type": "flex",
61 | "altText": "Flex Message",
62 | "contents": {
63 | "type": "bubble",
64 | "size": "giga",
65 | "body": {
66 | "type": "box",
67 | "layout": "vertical",
68 | "contents": [
69 | {
70 | "type": "box",
71 | "layout": "horizontal",
72 | "contents": [
73 | {
74 | "type": "image",
75 | "url": "https://mokmoon.com/images/LINEDEV.png",
76 | "aspectRatio": "3:1",
77 | "size": "lg",
78 | "align": "start",
79 | "flex": 1
80 | },
81 | {
82 | "type": "image",
83 | "url": "https://mokmoon.com/images/GDGBKK.png",
84 | "aspectRatio": "3:1",
85 | "size": "lg",
86 | "align": "start",
87 | "flex": 2,
88 | "margin": "none"
89 | }
90 | ],
91 | "paddingStart": "12%"
92 | },
93 | {
94 | "type": "box",
95 | "layout": "vertical",
96 | "contents": [
97 | {
98 | "type": "text",
99 | "text": "xxx",
100 | "contents": [
101 | {
102 | "type": "span",
103 | "text": "LINE HACK 2020",
104 | "size": "3xl",
105 | "color": "#ffffff",
106 | "weight": "bold"
107 | },
108 | {
109 | "type": "span",
110 | "text": ";",
111 | "weight": "bold",
112 | "color": "#0053ea",
113 | "size": "3xl"
114 | }
115 | ]
116 | },
117 | {
118 | "type": "text",
119 | "text": data.title,
120 | "color": "#00ff00",
121 | "weight": "bold",
122 | "size": "lg",
123 | "margin": "lg"
124 | },
125 | {
126 | "type": "text",
127 | "text": data.date,
128 | "color": "#ffffff",
129 | "weight": "bold",
130 | "size": "lg"
131 | }
132 | ],
133 | "paddingStart": "12%",
134 | "paddingTop": "4%",
135 | "paddingBottom": "8%",
136 | "paddingEnd": "8%"
137 | },
138 | {
139 | "type": "box",
140 | "layout": "horizontal",
141 | "contents": [
142 | {
143 | "type": "box",
144 | "layout": "vertical",
145 | "contents": [],
146 | "height": "32px",
147 | "cornerRadius": "sm",
148 | "background": {
149 | "type": "linearGradient",
150 | "angle": "0deg",
151 | "startColor": "#002e83",
152 | "endColor": "#0053ea",
153 | "centerColor": "#0053ea",
154 | "centerPosition": "32%"
155 | },
156 | "width": "12%"
157 | },
158 | {
159 | "type": "box",
160 | "layout": "vertical",
161 | "contents": [],
162 | "height": "32px",
163 | "cornerRadius": "sm",
164 | "background": {
165 | "type": "linearGradient",
166 | "angle": "0deg",
167 | "startColor": "#002e83",
168 | "endColor": "#0053ea",
169 | "centerColor": "#0053ea",
170 | "centerPosition": "32%"
171 | },
172 | "margin": "48px",
173 | "width": "64%"
174 | }
175 | ],
176 | "margin": "xxl"
177 | },
178 | {
179 | "type": "box",
180 | "layout": "horizontal",
181 | "contents": [
182 | {
183 | "type": "box",
184 | "layout": "vertical",
185 | "contents": [],
186 | "width": "24%",
187 | "height": "32px",
188 | "cornerRadius": "sm",
189 | "background": {
190 | "type": "linearGradient",
191 | "angle": "0deg",
192 | "startColor": "#002e83",
193 | "endColor": "#0053ea",
194 | "centerColor": "#0053ea",
195 | "centerPosition": "32%"
196 | }
197 | },
198 | {
199 | "type": "box",
200 | "layout": "vertical",
201 | "contents": [],
202 | "background": {
203 | "type": "linearGradient",
204 | "angle": "0deg",
205 | "startColor": "#008215",
206 | "endColor": "#00ea23",
207 | "centerColor": "#00ea23",
208 | "centerPosition": "32%"
209 | },
210 | "height": "32px",
211 | "width": "28%",
212 | "cornerRadius": "sm",
213 | "margin": "48px"
214 | }
215 | ],
216 | "margin": "xl"
217 | },
218 | {
219 | "type": "box",
220 | "layout": "horizontal",
221 | "contents": [
222 | {
223 | "type": "box",
224 | "layout": "vertical",
225 | "contents": [],
226 | "width": "25%",
227 | "height": "32px",
228 | "cornerRadius": "sm",
229 | "background": {
230 | "type": "linearGradient",
231 | "angle": "0deg",
232 | "startColor": "#002e83",
233 | "endColor": "#0053ea",
234 | "centerColor": "#0053ea",
235 | "centerPosition": "32%"
236 | }
237 | }
238 | ],
239 | "margin": "xl",
240 | "paddingStart": "12%"
241 | },
242 | {
243 | "type": "box",
244 | "layout": "vertical",
245 | "contents": [
246 | {
247 | "type": "text",
248 | "text": "HACK_CONNECT_TOMORROW_",
249 | "weight": "bold",
250 | "color": "#0053ea"
251 | }
252 | ],
253 | "paddingStart": "12%",
254 | "margin": "xxl"
255 | }
256 | ],
257 | "paddingBottom": "8%",
258 | "paddingEnd": "none",
259 | "paddingStart": "none",
260 | "paddingTop": "8%"
261 | },
262 | "styles": {
263 | "header": {
264 | "backgroundColor": "#1b1b1b"
265 | },
266 | "hero": {
267 | "backgroundColor": "#1b1b1b"
268 | },
269 | "body": {
270 | "backgroundColor": "#1b1b1b"
271 | }
272 | }
273 | }
274 | });
275 |
276 | module.exports = { linehack, linepay };
--------------------------------------------------------------------------------
/functions/util.js:
--------------------------------------------------------------------------------
1 | "use strict"
2 | const functions = require("firebase-functions");
3 | const axios = require("axios");
4 | const crypto = require("crypto");
5 |
6 | const LINE_USER_ID = functions.config().lineoa.jirawatee.userid;
7 | const LINE_MESSAGING_API = "https://api.line.me/v2/bot";
8 | const LINE_CHANNEL_SECRET = functions.config().lineoa.jirawatee.channel_secret;
9 | const LINE_HEADER = {
10 | "Content-Type": "application/json",
11 | Authorization: `Bearer ${functions.config().lineoa.jirawatee.channel_access_token}`
12 | };
13 |
14 | const getProfile = (userId) => {
15 | return axios({
16 | method: 'get',
17 | headers: LINE_HEADER,
18 | url: `${LINE_MESSAGING_API}/profile/${userId}`
19 | });
20 | };
21 |
22 | const getProfileGroup = (groupId, userId) => {
23 | return axios({
24 | method: 'get',
25 | headers: LINE_HEADER,
26 | url: `${LINE_MESSAGING_API}/group/${groupId}/member/${userId}`
27 | });
28 | };
29 |
30 | const getProfileRoom = (roomId, userId) => {
31 | return axios({
32 | method: 'get',
33 | headers: LINE_HEADER,
34 | url: `${LINE_MESSAGING_API}/room/${roomId}/member/${userId}`
35 | });
36 | };
37 |
38 | const reply = (token, payload) => {
39 | return axios({
40 | method: "post",
41 | url: `${LINE_MESSAGING_API}/message/reply`,
42 | headers: LINE_HEADER,
43 | data: JSON.stringify({
44 | replyToken: token,
45 | messages: [payload]
46 | })
47 | });
48 | };
49 |
50 | const push = (targetId, payload) => {
51 | return axios({
52 | method: "post",
53 | url: `${LINE_MESSAGING_API}/message/push`,
54 | headers: LINE_HEADER,
55 | data: JSON.stringify({
56 | to: targetId,
57 | messages: [payload]
58 | })
59 | });
60 | };
61 |
62 | const multicast = (payload) => {
63 | return axios({
64 | method: "post",
65 | url: `${LINE_MESSAGING_API}/message/multicast`,
66 | headers: LINE_HEADER,
67 | data: JSON.stringify({
68 | to: [LINE_USER_ID],
69 | messages: [payload]
70 | })
71 | });
72 | };
73 |
74 | const broadcast = (payload) => {
75 | return axios({
76 | method: "post",
77 | url: `${LINE_MESSAGING_API}/message/broadcast`,
78 | headers: LINE_HEADER,
79 | data: JSON.stringify({
80 | messages: [payload]
81 | })
82 | });
83 | };
84 |
85 | const postToDialogflow = async (req) => {
86 | req.headers.host = "dialogflow.cloud.google.com";
87 | return axios({
88 | url: `https://dialogflow.cloud.google.com/v1/integrations/line/webhook/${functions.config().dialogflow.agent_id}`,
89 | headers: req.headers,
90 | method: "post",
91 | data: req.body
92 | });
93 | };
94 |
95 | const verifySignature = (originalSignature, body) => {
96 | let text = JSON.stringify(body);
97 | text = text.replace(/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g, (e) => {
98 | return "\\u" + e.charCodeAt(0).toString(16).toUpperCase() + "\\u" + e.charCodeAt(1).toString(16).toUpperCase();
99 | });
100 | const signature = crypto.createHmac("SHA256", LINE_CHANNEL_SECRET).update(text).digest("base64").toString();
101 | if (signature !== originalSignature) {
102 | functions.logger.error("Unauthorized");
103 | return false;
104 | }
105 | return true;
106 | };
107 |
108 | module.exports = { reply, push, multicast, broadcast, getProfile, getProfileGroup, getProfileRoom, postToDialogflow, verifySignature };
--------------------------------------------------------------------------------