🎁 แกะกล่อง Follow Event อัพเดทใหม่ล่าสุดจาก Messaging API ที่จะทำให้รู้ว่าใครคือเพื่อนใหม่ที่แท้ทรูใน LINE OA

Thepnatee Phojan
LINE Developers Thailand
4 min readFeb 18, 2024

สวัสดีครับเพื่อนๆ บทความนี้รับรองว่า ต้องทำชาว LINE Developer ตื่นเต้นแน่นอน เพราะ LINE Webhook Event : “Follow” มีการ Update ใหม่นั้นเองงง….ซึ่งเขาได้ปล่อยหมัดเด็ดที่จะช่วยลดเวลาการเดฟให้ชาว LINE Developer ไปในพริบตา

https://developers.line.biz/en/news/2024/02/06/add-friends-and-unblock-friends-can-now-be-determined-by-webhook/

เมื่อวันที่ 6 กุมภาพันธ์ 2567 LINE ได้ Update Property มาตัวหนึ่ง ก็คือ follow.isUnblocked ซึ่งจะช่วยให้เพื่อนๆสามารถแยก ผู้ใช้งานใหม่จริงๆได้ดังนี้

follow.isUnblocked : false // ผู้ใช้ใหม่ของแท้
follow.isUnblocked : true // ผู้ใช้ที่เคย Block LINE OA นี้และกลับมาใหม่ Unblocked
https://developers.line.biz/en/reference/messaging-api/#follow-event

เป็นไงครับเพื่อนๆ พอจะนึก Use case เจ๋งๆ กันออกแล้วใช่มั้ยละ แถมลดการเขียน Code ได้ด้วยนะ แถมเราไม่ต้องเอา userId ไปนั่ง Check แล้วว่าเรา เคย Block หรือเปล่าใน Database ไปอี๊ก 5555+

Example Project : Coupons for new members

คราวนี้ผมลองมายกตัวอย่างกันเล่นๆสักอันหนึ่งครับ เพื่อให้เพื่อนๆ เห็นภาพมากขึ้น โดยโจทย์ง่ายๆ

  1. ผู้ใช้งานใหม่จะได้รับคูปอง 1 poin และ Welcome Message
  2. ผู้ใช้เก่าที่ทำการ Block ไปนานแล้ว และ Unblock กลับมาใหม่ จะได้รับข้อความ Welcome Back

และแน่นอนว่าก็ต้องมีการเตรียมตัวกันก่อนจะเริ่มดังนี้

  1. สร้าง LINE Developer Channel โดยสามารถทำตาม LINE Codelab ข้อ 1–4 ได้เลยครั

หรืออีกหนึ่งช่องจากบทความนี้ได้เลยครับ

2. Cloud Function for firebase 2nd Gen & Firebase Emulator

เพื่อนๆสามารถดูขั้นตอนการสร้าง Firebase Project ได้ที่บทความนี้ครับ ซึ่งรับรองว่าเพื่อนๆที่เคยพัฒนาด้วย Version แรกต้องมือสั่นแน่นอน เพราะ พี่ตี๋ Jirawatee การันตีเลยว่า นักพัฒนา LINE ด้วย Firebase ทุกคนชีวิตจะดีขึ้น 100%

3. Ngrok เครื่องมือหากินของเราที่ขาดไม่ได้

4. New LINE Developer Channel ซึ่ง แนะนำให้เพื่อนๆ ปิด Greeting Message และ Auto-Response Message เพื่อให้ได้ออรรถรส เต็มๆ

มาดูรูปตัวอย่างกันครับ

  • รูปทางซ้าย จะเป็น New Member ของแท้เลย
  • รูปด้านขวา จะป็น Old Member ที่ คิดถึงกันก็กลับมานะ… (Unblocked) 555+

โดยตัวอย่าง Code ผม ก็สั้นๆเลยครับง่ายๆเลย ตามนี้

File : index.js

const flex = require('./flex');
const line = require('./line.util');
const {
onRequest
} = require("firebase-functions/v2/https");
exports.webhook = onRequest(async (request, response) => {
if (request.method !== "POST") {
return response.send(request.method);
}
const events = request.body.events
for (const event of events) {
if (event.type === "follow") {
// Get LINE Profile for Send Display Name to Flex
const profile = await line.getProfile(event.source.userId)
// Check unBlocked : true => Old Member Welcome Back
if (event.follow.isUnblocked) {
await line.reply(event.replyToken, [flex.newWelcomeBackMemberMessage(profile.data.displayName)])
} else {
// Check unBlocked : flase => New Member
await line.reply(event.replyToken,
[flex.newWelcomeMemberMessage(profile.data.displayName),
flex.newWelcomeMemberFlex(profile.data.displayName)]
)
}
return response.end()
}
}
return response.end();
});

File : flex.js

exports.newWelcomeMemberMessage = (displaynName) => {
return {
"type": "flex",
"altText": "Welcome New Member",
"contents": {
"type": "bubble",
"body": {
"type": "box",
"layout": "vertical",
"contents": [{
"type": "text",
"text": "New Member",
"weight": "bold",
"size": "xl"
},
{
"type": "text",
"text": `Welcome, ${displaynName}`,
"margin": "md",
"wrap": true
},
{
"type": "text",
"text": "Points: 1 / 10",
"margin": "md",
"wrap": true
}
]
}
}
}
}
exports.newWelcomeBackMemberMessage = (displaynName) => {
return {
"type": "flex",
"altText": "Welcome Back Hooley!!",
"contents": {
"type": "bubble",
"body": {
"type": "box",
"layout": "vertical",
"contents": [{
"type": "text",
"text": "Welcome Back",
"weight": "bold",
"size": "xl"
},
{
"type": "text",
"text": "ยินดีต้อนกลับมาอีกครั้ง",
"margin": "md",
"wrap": true
},
{
"type": "text",
"text": `คุณ ${displaynName}`,
"margin": "md",
"wrap": true
}
]
}
}
}
}
exports.newWelcomeMemberFlex = (displaynName) => {
return {
"type": "flex",
"altText": "Welcome New Member",
"contents": {
"type": "bubble",
"size": "giga",
"body": {
"type": "box",
"layout": "vertical",
"contents": [{
"type": "image",
"url": "https://bucket.ex10.tech/images/ee748e89-cd7c-11ee-97d4-0242ac12000b/originalContentUrl.png",
"aspectRatio": "21:9",
"aspectMode": "cover",
"size": "full"
},
{
"type": "image",
"url": "https://bucket.ex10.tech/images/4907b077-cd7f-11ee-97d4-0242ac12000b/originalContentUrl.jpg",
"offsetBottom": "110px",
"offsetEnd": "125px",
"size": "40px"
},
{
"type": "text",
"text": displaynName,
"offsetBottom": "195px",
"offsetStart": "80px"
}
],
"height": "200px"
}
}
}
}

File : line.util.js

const functions = require("firebase-functions");
const axios = require("axios");
const qs = require('qs');
const LINE_MESSAGING_API = process.env.LINE_MESSAGING_API;
const LINE_MESSAGING_OAUTH_ISSUE_TOKENV3 = process.env.LINE_MESSAGING_OAUTH_ISSUE_TOKENV3;
const LINE_CHANNEL_ID = process.env.LINE_CHANNEL_ID;
const LINE_CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET;
exports.getProfile = async (userId) => {
const issue_token = await issueTokenV3()
const access_token = issue_token.access_token
console.log(access_token);
console.log(`${LINE_MESSAGING_API}/profile/${userId}`);
return await axios({
method: 'get',
maxBodyLength: Infinity,
url: `${LINE_MESSAGING_API}/profile/${userId}`,
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
},
})
};
exports.reply = async (token, payload) => {
const issue_token = await issueTokenV3()
const access_token = issue_token.access_token
return axios({
method: "post",
url: `${LINE_MESSAGING_API}/message/reply`,
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
},
data: JSON.stringify({
replyToken: token,
messages: payload
})
});
};
/* Stateless Channel Access Token : 15 minute */
async function issueTokenV3() {
let data = qs.stringify({
'grant_type': 'client_credentials',
'client_id': LINE_CHANNEL_ID,
'client_secret': LINE_CHANNEL_SECRET
});
let response = await axios({
method: 'post',
maxBodyLength: Infinity,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
url: LINE_MESSAGING_OAUTH_ISSUE_TOKENV3,
data: data
})
return response.data
}

File : .env

#  LINE Messaging API
# ------------------------------------------------------------------------
# Domain name LINE API
# https://developers.line.biz/en/reference/messaging-api/#domain-name
# Other API endpoints
LINE_MESSAGING_API='https://api.line.me/v2/bot'
#
# Endpoint Stateless Channel Access Token
LINE_MESSAGING_OAUTH_ISSUE_TOKENV3="https://api.line.me/oauth2/v3/token"
#
#
# LINE Developer Console -> LINE Messaging API -> Basic Setting
LINE_CHANNEL_ID='channel Id'
LINE_CHANNEL_SECRET='channel secret'

เอาละครับ จบเรียบร้อย เพียงเท่านี้เพื่อนๆ ก็สามารถจับได้แล้วว่าใคร..คือสมาชิกใหม่ของแทร่.. ฝากบทความเทคนิคสั้นๆ หวังว่าจะมีประโยชน์กับเพื่อนๆทุกคนนะครับ ขอให้สนุกกับการพัฒนาแล้วพบกันใหม่บทความหน้านะครับ 😊👋

--

--