Hướng dẫn tạo Slack Bot bằng NestJS
Giới thiệu
Slack Bot là một công cụ hữu ích giúp tự động hóa các tác vụ và tăng cường tương tác trong không gian làm việc Slack. Với khả năng lắng nghe sự kiện, phản hồi tin nhắn, thực thi các lệnh tùy chỉnh (Slash Commands), bot có thể trở thành trợ thủ đắc lực cho đội nhóm của bạn. Trong bài viết này, chúng ta sẽ cùng nhau xây dựng một Slack Bot đơn giản nhưng đầy đủ chức năng cơ bản bằng NestJS, một framework Node.js mạnh mẽ và có cấu trúc rõ ràng. Bot của chúng ta sẽ hỗ trợ Slash Command và lắng nghe một số sự kiện phổ biến từ Slack.
Phần 1: Tạo Slack App và Cấu hình trên Slack
Trước khi viết code, chúng ta cần tạo một ứng dụng trên nền tảng Slack và cấu hình các quyền cũng như điểm cuối (endpoints) cần thiết.
Bước 1: Truy cập Slack API và tạo App
- Truy cập Slack API Dashboard và đăng nhập vào tài khoản Slack của bạn.
- Nhấn Create New App để tạo một ứng dụng Slack mới.
- Chọn From Scratch để tạo ứng dụng từ đầu.
- Đặt tên App (ví dụ:
NestSlackBot
) và chọn Workspace nơi bạn muốn cài đặt và thử nghiệm bot này. - Nhấn Create App.
Bước 2: Cấu hình quyền (OAuth Scopes)
Quyền (Scopes) xác định những gì bot của bạn được phép làm trong Workspace.
-
Trong menu bên trái của trang quản lý ứng dụng, chọn OAuth & Permissions.
-
Kéo xuống phần Scopes. Trong mục Bot Token Scopes, nhấn Add an OAuth Scope.
-
Thêm các quyền sau:
chat:write
: Cho phép bot gửi tin nhắn với tư cách là chính nó.commands
: Cho phép ứng dụng đăng ký và sử dụng Slash Commands.app_mentions:read
: Cho phép bot đọc các tin nhắn có @mention tên bot.channels:join
: Cho phép bot tham gia các kênh công khai (public channels). (Lưu ý: Scope này cho phép bot tự tham gia, hữu ích nhưng cần cân nhắc về quyền riêng tư).im:history
: Cho phép bot đọc lịch sử tin nhắn trong các cuộc trò chuyện trực tiếp (DM) với người dùng.im:write
: Cho phép bot bắt đầu cuộc trò chuyện trực tiếp (DM) với người dùng.
-
Sau khi thêm đủ scopes, kéo lên đầu trang và nhấn Install to Workspace trong phần OAuth Tokens for Your Workspace.
-
Xem lại các quyền và nhấn Allow.
-
Sau khi cài đặt thành công, bạn sẽ thấy Bot User OAuth Token (thường bắt đầu bằng xoxb-...). Hãy sao chép và lưu lại token này một cách an toàn, chúng ta sẽ cần nó ở phần sau (SLACK_BOT_TOKEN).
Bước 3: Kích hoạt lắng nghe Sự kiện (Event Subscriptions)
-
Vào Event Subscriptions trong menu giao diện quản lý ứng dụng Slack và bật Enable Events.
-
Nhập Request URL: https://your-server.com/slack/events (sẽ cấu hình sau trong NestJS).
-
Trong phần Subscribe to bot events, thêm các sự kiện:
-
message.channels
: Nhận tin nhắn từ kênh công khai. -
message.im
: Nhận tin nhắn riêng tư. -
app_mention
: Khi bot được nhắc đến trong cuộc trò chuyện.
-
-
Nhấn Save Changes để lưu cài đặt.
Bước 4: Bật Slash Command
Chúng ta sẽ tạo một lệnh /demobot
đơn giản.
-
Trong menu bên trái, chọn Slash Commands.
-
Nhấn Create New Command.
-
Điền các thông tin sau:
- Command:
/demobot
(Đây là lệnh người dùng sẽ gõ trên Slack). - Request URL: Nhập URL mà Slack sẽ gửi thông tin khi lệnh này được gọi. Tương tự như Event Subscriptions, định dạng sẽ là https://your-server.com/slack/commands. Chúng ta sẽ cấu hình URL này sau.
- Short Description: Mô tả ngắn gọn về lệnh (ví dụ: "A simple Demo bot command").
- (Optional) Usage Hint: Gợi ý cách dùng lệnh (ví dụ: [your text]).
- Command:
-
Nhấn Save.
-
Sau khi lưu, bạn có thể cần cài đặt lại ứng dụng vào Workspace (Reinstall App) để lệnh mới có hiệu lực. Quay lại tab OAuth & Permissions hoặc tìm thông báo yêu cầu cài đặt lại ở đầu trang.
Vậy là chúng ta đã hoàn thành việc cấu hình cơ bản trên Slack. Đừng quên lưu lại Bot User OAuth Token và Signing Secret (lấy từ tab Basic Information -> App Credentials) nhé!
Phần 2: Xây dựng Logic Bot với NestJS
Bây giờ, chúng ta sẽ bắt tay vào viết code cho ứng dụng NestJS.
Bước 1: Cài đặt Project NestJS và Thư viện
Trước tiên, hãy đảm bảo bạn đã cài NestJS CLI. Nếu chưa, chạy lệnh (khuyến nghị dùng yarn hoặc npm):
# Dùng yarn
yarn global add @nestjs/cli
# Hoặc dùng npm
# npm install -g @nestjs/cli
Kiểm tra xem NestJS đã được cài đặt thành công:
nest -v
Tạo một dự án NestJS mới có tên là slack-bot (sử dụng yarn làm trình quản lý package):
nest new slack-bot --package-manager yarn
Chuyển vào thư mục dự án:
cd slack-bot
Cài đặt các package cần thiết:
# Thư viện chính để tương tác với Slack API và Events
yarn add @slack/bolt
# Hỗ trợ đọc biến môi trường từ file .env
yarn add dotenv @nestjs/config
Thử khởi chạy server NestJS:
yarn start:dev
Bạn sẽ thấy thông báo server đang chạy, thường là trên port 3000. Nhấn Ctrl + C
để dừng lại.
Bước 2: Thiết lập Biến Môi trường trong NestJS
Để bảo mật các token và thông tin nhạy cảm, chúng ta sẽ sử dụng biến môi trường.
-
Tạo file .env: Tạo file .env ở thư mục gốc của dự án (slack-bot/.env) với nội dung sau:
# Lấy từ Basic Information -> App Credentials trong trang quản lý Slack App SLACK_SIGNING_SECRET=your_slack_signing_secret # Lấy từ OAuth & Permissions -> OAuth Tokens for Your Workspace SLACK_BOT_TOKEN=xoxb-your-slack-bot-token # (Tùy chọn) Port chạy ứng dụng NestJS PORT=3000
- Thay thế your_slack_signing_secret và xoxb-your-slack-bot-token bằng giá trị thực tế bạn đã lấy được ở Bước 2 và từ tab Basic Information trên Slack App.
- Thêm file .env vào file .gitignore của bạn để tránh vô tình đưa lên Git.
-
Cấu hình ConfigModule: Mở file
src/app.module.ts
và cập nhật để NestJS đọc được file.env
:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; // Import ConfigModule
import { AppController } from './app.controller';
import { AppService } from './app.service';
// Import SlackModule sẽ tạo ở bước sau
import { SlackModule } from './slack/slack.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
SlackModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Bước 3: Tạo Slack Module và Service
Chúng ta sẽ tạo một module riêng (SlackModule
) và một service (SlackService
) để quản lý logic liên quan đến Slack, giúp cấu trúc code rõ ràng hơn.
Chạy các lệnh sau trong terminal:
# Tạo module Slack
nest generate module slack
# Tạo service Slack
nest generate service slack
Bây giờ, chúng ta sẽ cấu hình và khởi tạo Slack Bolt App bên trong SlackService
. Mở file src/slack/slack.service.ts
và cập nhật nội dung:
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { App as SlackBoltApp, ExpressReceiver } from '@slack/bolt';
@Injectable()
export class SlackService implements OnModuleInit {
private readonly logger = new Logger(SlackService.name);
private boltApp: SlackBoltApp;
private boltReceiver: ExpressReceiver;
constructor(private readonly configService: ConfigService) {
this.boltReceiver = new ExpressReceiver({
signingSecret: this.configService.getOrThrow<string>('SLACK_SIGNING_SECRET'), // Lấy signing secret từ .env
endpoints: '/slack/events', // Chỉ định endpoint cho events, Bolt sẽ tự xử lý URL verification tại đây
processBeforeResponse: true, // Nên đặt là true để xử lý logic trước khi gửi response về Slack
});
this.boltApp = new SlackBoltApp({
token: this.configService.getOrThrow<string>('SLACK_BOT_TOKEN'), // Lấy bot token từ .env
receiver: this.boltReceiver,
processBeforeResponse: true,
});
}
async onModuleInit() {
this.registerListeners();
this.logger.log('Slack Bolt App Initialized and Listeners Registered');
}
getReceiver(): ExpressReceiver {
return this.boltReceiver;
}
getBoltApp(): SlackBoltApp {
return this.boltApp;
}
private registerListeners(): void {
this.logger.log('Registering Slack listeners...');
this.boltApp.command('/demobot', async ({ command, ack, say, client, logger }) => {
await ack();
logger.log(`Received /demobot command from user ${command.user_id}`);
const userId = command.user_id;
const textPayload = command.text; // Lấy phần text người dùng nhập sau lệnh
try {
await client.chat.postMessage({
channel: userId, // Để gửi DM, channel chính là User ID
text: `Chào <@${userId}>! Bạn đã gọi /demobot ${textPayload ? `với nội dung: "${textPayload}"` : ''}. Rất vui được hỗ trợ!`,
});
logger.log(`Responded to /demobot command from user ${userId}`);
} catch (error) {
logger.error(`Failed to handle /demobot for user ${userId}: ${error.message || error}`);
try {
await client.chat.postMessage({
channel: userId,
text: `Rất tiếc, đã có lỗi xảy ra khi thực hiện lệnh /demobot.`
});
} catch (postError) {
logger.error(`Failed to send error DM to user ${userId}: ${postError.message || postError}`);
}
}
});
this.boltApp.event('app_mention', async ({ event, say, logger }) => {
logger.log(`Received app_mention event from user ${event.user} in channel ${event.channel}`);
try {
await say({
text: `Chào <@${event.user}>, bạn cần giúp gì ạ? :blush:`,
thread_ts: event.thread_ts || event.ts // Trả lời trong thread nếu có
});
} catch (error) {
logger.error(`Failed to handle app_mention: ${error.message || error}`);
}
});
this.boltApp.message(async ({ message, event, say, logger }) => {
if (message.channel_type === 'im' && !message.subtype && !event.bot_id) {
logger.log(`Received direct message from user ${event.user}: "${message.text}"`);
try {
await say(`Tôi nhận được tin nhắn của bạn: "${message.text}". Hiện tại tôi chưa xử lý được nhiều :sweat_smile:`);
} catch (error) {
logger.error(`Failed to handle direct message: ${error.message || error}`);
}
}
});
this.logger.log('Slack listeners registered successfully!');
}
}
Giải thích chi tiết:
- Chúng ta dùng
ConfigService
của NestJS để lấy các biến môi trường một cách an toàn (getOrThrow
sẽ báo lỗi nếu biến không tồn tại). ExpressReceiver
được cấu hình với signingSecret và endpoints. Việc chỉ định endpoints ở đây giúp Bolt tự động xử lý URL Verification mà Slack yêu cầu khi bạn lưu Request URL trong Event Subscriptions.SlackBoltApp
được khởi tạo với token và receiver.onModuleInit()
là nơi lý tưởng để gọiregisterListeners()
, đảm bảo mọi thứ sẵn sàng trước khi bắt đầu lắng nghe.- Trong
registerListeners()
:boltApp.command()
: Đăng ký xử lý cho Slash Command. Lưu ý việc gọi ack() sớm và cách gửi DM bằng client.chat.postMessage với channel: userId.boltApp.event('app_mention', ...)
: Đăng ký xử lý khi bot được nhắc đến.boltApp.message(...)
: Đây là cách khác để lắng nghe sự kiện message. Bolt cung cấp các hàm helper như message() để lắng nghe sự kiện này (thay vìevent('message', ...)
). Chúng ta thêm điều kiện lọc để chỉ xử lý tin nhắn DM từ người dùng thực.
Bước 4: Tạo Slack Controller
Controller này đóng vai trò cầu nối giữa HTTP requests từ Slack và ExpressReceiver
của Bolt.
Chạy lệnh sau:
nest generate controller slack
Mở file src/slack/slack.controller.ts
và cập nhật:
import { Controller, Post, Req, Res, Logger, Get, HttpCode } from '@nestjs/common';
import { Request, Response } from 'express';
import { SlackService } from './slack.service';
import { ExpressReceiver } from '@slack/bolt';
@Controller('slack')
export class SlackController {
private readonly logger = new Logger(SlackController.name);
private receiver: ExpressReceiver;
constructor(private readonly slackService: SlackService) {
this.receiver = this.slackService.getReceiver();
}
@Post('events')
async handleEvents(@Req() req: Request, @Res() res: Response) {
await this.receiver.requestHandler(req, res);
}
@Post('commands')
async handleCommands(@Req() req: Request, @Res() res: Response) {
await this.receiver.requestHandler(req, res);
}
}
Giải thích:
- Controller định nghĩa các route POST
/slack/events
và POST/slack/commands
. - Quan trọng nhất là việc gọi
this.receiver.requestHandler(req, res)
trong mỗi route. Hàm này của Bolt là "trái tim" xử lý mọi request đến từ Slack, đảm bảo tính đúng đắn và định tuyến đến các listeners bạn đã viết trong SlackService.
Bước 5: Chạy và Kiểm tra
- Cập nhật Request URL:
- Bạn cần một URL công khai để Slack có thể gửi request đến ứng dụng NestJS của bạn. Trong quá trình phát triển, bạn có thể sử dụng các công cụ như ngrok hoặc localtunnel.
- Ví dụ với ngrok: Chạy
ngrok http 3000
(nếu app NestJS chạy ở port 3000). Ngrok sẽ cung cấp cho bạn một URL dạnghttps://<random_string>.ngrok.io
- Quay lại trang quản lý Slack App:
- Vào Event Subscriptions, cập nhật Request URL thành
https://<random_string>.ngrok.io/slack/events.
Slack sẽ cố gắng verify URL này, nếu NestJS app đang chạy và receiver được cấu hình đúng, nó sẽ thành công. - Vào Slash Commands, chỉnh sửa lệnh
/demobot
và cập nhật Request URL thànhhttps://<random_string>.ngrok.io/slack/commands
.
- Vào Event Subscriptions, cập nhật Request URL thành
- Lưu ý: Mỗi khi khởi động lại ngrok, bạn sẽ có URL mới và cần cập nhật lại trên Slack. Đối với môi trường Staging/Production, bạn sẽ dùng URL công khai thực tế của server.
- Khởi chạy ứng dụng NestJS:
yarn start:dev
- Kiểm tra trong Slack:
- Mở Workspace Slack của bạn.
- Thử Slash Command: Gõ
/demobot
và nhấn Enter. Bot của bạn (nếu đang chạy và nhận được request từ Slack qua ngrok) sẽ gửi một tin nhắn trực tiếp cho bạn. - Thử @mention: Trong một kênh mà bạn đã mời bot vào (hoặc trong App Home của bot), gõ
@Tên_Bot_Của_Bạn Chào bot!
(ví dụ:@NestSlackBot Chào bot!
). Bot sẽ phản hồi lại trong kênh đó. - Thử DM: Gửi một tin nhắn trực tiếp cho bot. Bot sẽ echo lại tin nhắn của bạn.
- Xem Logs: Theo dõi cửa sổ terminal nơi bạn chạy
yarn start:dev
để xem các log từ Logger, giúp gỡ lỗi nếu có vấn đề.
Kết luận
Chúc mừng! Bạn đã xây dựng thành công một Slack Bot cơ bản bằng NestJS, có khả năng xử lý Slash Command và các sự kiện như tin nhắn, mention. Từ nền tảng này, bạn có thể mở rộng thêm nhiều tính năng phức tạp hơn như tương tác với các nút bấm, menu, modals, tích hợp với các API khác, lưu trữ dữ liệu, v.v.
NestJS cung cấp một cấu trúc vững chắc để xây dựng các ứng dụng Slack phức tạp, trong khi thư viện @slack/bolt giúp đơn giản hóa việc tương tác với API và sự kiện của Slack.
Hy vọng phần tiếp theo này đáp ứng được yêu cầu của bạn! Chúc bạn xây dựng được những Slack Bot tuyệt vời với NestJS.
All rights reserved