ทดลองทำ Slack Slash Command
ปัจจุบันจะเห็นว่าหลายๆบริษัทได้นำเอา Slack มาใช้ในการคุยงานกันมากขึ้น รวมถึงที่ Nextzy Technologies ก็เช่นกัน เนื่องจากทุกๆวัน ต้องเปิดหน้าจอ Slack ตลอด ก็เลยอยากจะลองเขียน Slash Command ขึ้นมา เพื่อจะได้ไม่ต้องสลับจอบ่อยๆ จึงเกิดเป็นบทความนี้ขึ้นมา นั่นเอง
บทความนี้ผมใช้วิธีเขียน Slash Command โดยฝั่ง Server เขียนด้วย Hapijs และใช้Digital Ocean สำหรับรัน Server
Slack Slash Command คืออะไร ?
Slash Command คือ ชุดคำสั่งที่ให้เราสามารถจะพิมพ์หรือดึงข้อมูลต่างๆผ่านหน้า Slack Chat ได้เลย โดยการพิมพ์ /
แล้วตามด้วยคำสั่งที่เราเขียนไว้
หลักการทำงาน
หลักการทำงานของ Slash Command คือ
- เมื่อผู้ใช้งานพิมพ์คำสั่ง
/
slash command มันก็จะถูกส่งHTTP POST
ไปยัง Server ของเรา - Server ทำการ validate ว่าเป็นคำสั่งที่ส่งมาจาก slack จริงหรือไม่ โดยอาจจะใช้token ที่ส่งมา
- ถ้าจริงก็ส่งข้อมูลกลับไปที่ slack ตาม format ที่ถูกต้อง
Step 1 : Create Slash Commands Service
เริ่มแรกให้เข้าไปที่ New Slash Commands เพิ่อเปิดใช้งาน Slash Service (จำเป็นที่จะต้องมี permission ในทีมนั้นๆด้วย) หรือจะเข้าไปหาจาก Slack App ก็ได้
เมื่อเรากด สร้างเสร็จ ก็จะมาถึงหน้า Integrating Setting ก็ให้เรากำหนด
- Command : อันนี้เอาไว้กำหนดว่า เราต้องการแบบไหน ของผมใช้
/ahoy
- URL : (อันนี้ต้องเป็น server จริงๆ ของผมสร้าง Droplet ใน Digital Ocean มาตัวนึง)
- Method : เราจะให้ส่ง HTTP เป็นชนิดอะไรก็กำหนดไว้
- Token : อันนี้สำคัญครับ เป็นความลับห้ามให้ใครรู้ ควรทำการ validate ข้อมูลทุกครั้งที่ส่ง
สุดท้ายเรียบร้อยแล้ว เราสามารถใช้คำสั่ง /ahoy
ผม Slack ได้แล้ว แต่ว่ายังไม่มีอะไรเกิดขึ้น เนื่องจากเรายังไม่ได้ทำการ implement ฝั่ง Server เลย
Step 2 : Create Server
ตัว Server ผมใช้ Hapijs ในการทำ สร้างโปรเจ็คด้วย npm init
หรือแบบไหนตามสะดวก
ติดตั้ง dependencies เหล่านี้ลงไป
npm install hapi chalk dotenv --save
ส่วนของผมจะได้ไฟล์ package.json
หลังจากติดตั้ง dependencies ประมาณนี้
{
"name": "slack-slash-command",
"version": "1.0.0",
"dependencies": {
"chalk": "^1.1.3",
"dotenv": "^2.0.0",
"hapi": "^13.4.0"
},
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
เนื่องจากฝั่ง Server เราแค่กำหนด route ที่ต้องการจะใช้ Slack ส่งข้อมูลมาตอนที่เราพิมพ์ command ฉะนั้นทางฝั่ง server ผมก็กำหนดไว้แค่
{
method: 'POST',
path: '/ahoy',
handler: (request, reply) => {
let payload = request.payload;
}
}
ซึ่งเป็น path เดียวกันกับที่ผมกำหนด URL ในส่วน Integrating Setting นั่นเอง
หลังจากนั้นผมทำการสร้าง server.js
, routes.js
, controller.js
และ logger.js
ขึ้นมา เพื่อให้มัน handle http://MY_WEB_URL/ahoy
แบบง่ายๆจาก slack ได้
ไฟล์ server.js
'use strict'
const Hapi = require('hapi')
const server = new Hapi.Server()
const chalk = require('chalk')
const routes = require('./routes')
require('dotenv').config()
server.connection({
host: 'localhost',
port: 2345
})
server.route(routes)
server.start(() => {
console.log(chalk.green(`Server is running at ${server.info.uri}`))
})
ไฟล์ controller.js
'use strict'
const logger = require('./logger')
module.exports = {
index: {
handler: (request, reply) => {
reply({ message: 'OK' })
}
},
ahoy: {
handler: (request, reply) => {
let payload = request.payload
logger(payload, 'payload')
reply('Ahoy! How are you?')
}
}
}
ไฟล์ routes.js
'use strict'
const controller = require('./controller')
module.exports = [
{
method: 'GET',
path: '/',
config: controller.index
},
{
method: 'POST',
path: '/ahoy',
config: controller.ahoy
}
]
ไฟล์ logger.js
'use strict'
const chalk = require('chalk')
module.exports = (message, name) => {
if (typeof message === 'object') {
console.log(chalk.blue('============================'))
console.log(chalk.blue(name))
console.log(chalk.blue(JSON.stringify(message, undefined, 2)))
console.log(chalk.blue('============================'))
} else {
console.log(chalk.blue('============================'))
console.log(chalk.blue(name))
console.log(chalk.blue(message))
console.log(chalk.blue('============================'))
}
}
โค๊ดส่วนนี้ผมการอัพขึ้น Hosting จริง มันจึงสามารถรับ trigger จาก slack ได้ หากเรารัน server บน localhost จะไม่สามารถรับ trigger ได้นะครับ :)
ทดลองพิมพ์ข้อความใน Slack ดูใหม่
Step 3 : Implement Logic
ต่อมาเมื่อระบบโอเคแล้ว สามารถรับส่งข้อมูลระหว่าง Slack ได้แล้ว ต่อมาเราต้องมาทำตาม Guideline ของ Slack กันบ้าง ก็คือต้องทำการเช็ค validate ทุก request ฉะนั้นผมจึงทำการสร้างไฟล์ local variable แต่ขี้เกียจเซฟไว้ในเครื่องเลยใช้เป็น .env
และใช้ https://www.npmjs.com/package/dotenv มาช่วย
สร้างไฟล์ .env
ขึ้นมา ใส่ Slack Token ลงไป
SLACK_TOKEN=YOUR_SLACK_TOKEN_ID
จากนั้นในส่วน controller.js
ผมก็จะเช็ค payload ที่ทาง slack ส่งมาก่อน เนื่องจากว่าส่งมาเป็น POST
payload ทั้งหมดที่ Slack ส่งมาคือ
{
"token": "xxxxx",
"team_id": "xxxxx",
"team_domain": "devahoy",
"channel_id": "xxxxx",
"channel_name": "xxxxx",
"user_id": "xxxxx",
"user_name": "xxxxx",
"command": "/ahoy",
"text": "ahoy",
"response_url": "xxxxx"
}
สิ่งที่เราต้องการตอนนี้คือ
token
: เพื่อเอาไว้เช็คว่าเป็น request จาก Slack จริงไหมcommand
: อันนี้เอาไว้ตรวจสอบว่าผู้ใช้พิมพ์คำสั่งอะไรมา แต่ว่าเนื่องจากตอนนี้เรามีแค่คำสั่งเดียว ส่วนนี้อาจจะยังไม่จำเป็นtext
: ข้อความที่ถูกส่งมาพร้อมกับ command
ในส่วน logic ผมก็เลยเขียนใหม่เป็น
handler: (request, reply) => {
let payload = request.payload
logger(payload, 'payload')
if (payload.token !== process.ENV.SLACK_TOKEN) {
return reply('unauthorized')
}
reply(`Ahoy! ${payload.user_name} How are you?`)
}
เนื่องจาก Slack สามารถ Custom response ข้อความได้ว่าจะส่งเป็นแบบ plain text หรือว่าจะส่งแบบกำหนด format เช่น เห็นแบบ private เฉพาะคนพิมพ์ หรือเห็นทั้ง channel (default เป็น private) เช่น
{
"response_type": "in_channel",
"text": "Good morning!",
"attachments": [
{
"text": "How are you today?"
}
]
}
ซึ่งผมจะใช้ payload.text
เป็นการกำหนดว่าให้ส่ง response กลับไปแบบไหน
if (payload.text === 'private') {
return reply(`Ahoy! ${payload.user_name} How are you?`)
} else {
return reply({
response_type: 'in_channel',
text: `Ahoy! ${payload.user_name}`,
attachments: [{ text: `How are you? ${payload.user_name}` }]
})
}
เมื่อลองพิมพ์บน Slack ก็จะได้แบบดังภาพ เป็นอันเรียบร้อย :)
นอกเหนือจากนี้เรายังสามารถ Custom logic ต่างๆทางฝั่ง Server ของเรา อาจจะมีการคำนวณ +-*/ คร่าวๆ ดึงข้อมูลสภาพอากาศ เช็คผลบอล เช็คโปรแกรมการแข่งขันต่าง ๆ หรือเช็ครอบหนัง แล้วส่งกลับไป Slack ก็ทำได้ ก็ลองๆเอาไปประยุกต์กันดูครับ หรือจะ Advanced กว่านั้นทำเป็น Slack App ไปเลยก็ได้ครับ
สุดท้าย Source Code ที่ใช้ในบทความผมอัพไว้บน Github ครับ
Reference
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit