Initial commit

This commit is contained in:
Victor Phan
2026-07-17 20:29:31 +07:00
commit caa9717571
4 changed files with 300 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
[
{
"message": {
"type": 1,
"data": {
"actionId": "13593331124451",
"msgId": "8053568045365",
"cliMsgId": "1784291715396",
"msgType": "chat.recommended",
"uidFrom": "1934774705109471677",
"idTo": "1435552796197334408",
"dName": "Hiển Phan",
"ts": "1784291715456",
"status": 1,
"content": {
"title": "https://vn.shp.ee/rv4j1m3f?smtt=0.0.9",
"description": "Thùng Máy Fujitsu SFF Đồng Bộ Zin Tháo Máy -Chỉ Main Case Nguồn Không CPU RAM SSD - Bảo Hành 3 Tháng | 0 likes , watch @ext20vi1bj's video in Shopee Video",
"href": "https://vn.shp.ee/rv4j1m3f?smtt=0.0.9",
"thumb": "",
"childnumber": 0,
"action": "recommened.link",
"params": "{\"redirect_url\":\"\",\"src\":\"vn.shp.ee\",\"mediaTitle\":\"ext20vi1bj on Shopee Video\",\"streamUrl\":\"\",\"type\":0,\"linkType\":0,\"artist\":\"\",\"count\":\"\",\"stream_icon\":\"\",\"mediaId\":\"\",\"video_duration\":0,\"arid\":0,\"href\":\"https:\\/\\/vn.shp.ee\\/rv4j1m3f?smtt=0.0.9\",\"tType\":1,\"tWidth\":0,\"tHeight\":0,\"icon\":\"https:\\/\\/deo.shopeemobile.com\\/shopee\\/shopee-pcmall-live-sg\\/assets\\/icon_favicon_1_96.wI1aMs.png\",\"thumb_src_type\":1}",
"type": ""
},
"notify": "1",
"ttl": 0,
"userId": "0",
"uin": "0",
"topOut": "0",
"topOutTimeOut": "0",
"topOutImprTimeOut": "0",
"propertyExt": {
"color": -1,
"size": -1,
"type": -1,
"subType": 0,
"ext": "{\"sSrcType\":-1,\"sSrcStr\":\"\",\"msg_warning_type\":0,\"emoji\":{\"content\":0,\"num\":0,\"uniq\":0,\"first\":\"\",\"last\":\"\",\"most\":\"\",\"text\":1}}"
},
"paramsExt": {
"countUnread": 1,
"containType": 0,
"platformType": 0
},
"cmd": 521,
"st": 3,
"at": 0,
"realMsgId": "0"
},
"threadId": "1435552796197334408",
"isSelf": true
}
}
]
+1
View File
@@ -0,0 +1 @@
ID group zalo: 1435552796197334408
+181
View File
@@ -0,0 +1,181 @@
# Hướng dẫn thiết lập n8n và Zalo Bot cho Group
Để tạo một bot Zalo có thể hoạt động trong các Group (nhóm chat bình thường, không phải Official Account), bạn cần kết hợp **n8n** với một **Mã nguồn Node.js trung gian**.
Lý do: Zalo Official Account (Zalo OA) mặc định không cho phép bot tự động tham gia các nhóm chat cá nhân. Vì vậy, ta phải dùng một "tài khoản Zalo cá nhân" (acc clone) và dùng Node.js làm cầu nối với n8n.
## Kiến trúc hoạt động
```mermaid
sequenceDiagram
participant Zalo Group
participant Node.js (Zalo Client)
participant n8n Workflow
Zalo Group->>Node.js (Zalo Client): 1. User nhắn tin vào Group
Node.js (Zalo Client)->>n8n Workflow: 2. Gọi Webhook n8n (Forward tin nhắn)
n8n Workflow->>n8n Workflow: 3. Xử lý tin nhắn (Tạo link affiliate, AI, Keyword...)
n8n Workflow->>Node.js (Zalo Client): 4. Gọi HTTP Request về Node.js (Gửi câu trả lời)
Node.js (Zalo Client)->>Zalo Group: 5. Bot nhắn tin phản hồi vào Group
```
## Các bước thực hiện chi tiết
### Bước 1: Tạo Node Webhook trên n8n
1. Mở n8n và tạo một workflow mới.
2. Thêm Node **Webhook**:
- Authentication: None (có thể thêm sau để bảo mật).
- HTTP Method: **POST**
- Path: `zalo-receive`
3. Copy **Test URL** của webhook (Ví dụ: `http://localhost:5678/webhook-test/zalo-receive`).
4. Nhấn nút **"Listen for test event"** để chờ dữ liệu.
### Bước 2: Viết mã nguồn Node.js (Zalo Client)
Mã nguồn này sẽ đóng giả làm một tài khoản cá nhân, nhận tin nhắn và đẩy sang n8n.
Bạn cần khởi tạo project (bạn có thể tạo ngay trong thư mục `botAffilate`):
```bash
npm init -y
npm install express axios zca-js
```
*(Lưu ý: `zca-js` hoặc `zalo-baileys` là các thư viện unofficial phổ biến để login Zalo cá nhân, bạn có thể chọn thư viện phù hợp nhất đang còn hoạt động tốt).*
Tạo file `server.js` với cấu trúc cơ bản:
```javascript
const express = require('express');
const axios = require('axios');
const { Zalo } = require('zca-js'); // Tùy thuộc vào thư viện bạn chọn
const app = express();
app.use(express.json());
// 1. Cấu hình
const N8N_WEBHOOK = 'http://127.0.0.1:5678/webhook-test/zalo-receive';
const API_PORT = 3000;
// 2. Khởi tạo Bot Zalo cá nhân
const zalo = new Zalo({
cookie: "COOKIE_ZALO_TRÊN_TRÌNH_DUYỆT_CỦA_BẠN",
imei: "IMEI_CỦA_BẠN"
});
zalo.login().then(() => {
console.log("✅ Đăng nhập Zalo thành công!");
// 3. Bắt sự kiện có tin nhắn mới trong Group
zalo.on('message', async (msg) => {
if (msg.isGroup) {
console.log(`[Group ${msg.groupId}] ${msg.senderId}: ${msg.text}`);
// Bắn tin nhắn qua cho n8n xử lý
try {
await axios.post(N8N_WEBHOOK, {
groupId: msg.groupId,
senderId: msg.senderId,
text: msg.text
});
} catch (err) {
console.error("Lỗi khi gửi qua n8n:", err.message);
}
}
});
});
// 4. Tạo API để n8n gọi lại khi cần gửi tin nhắn
app.post('/api/send', async (req, res) => {
const { groupId, message } = req.body;
if (!groupId || !message) {
return res.status(400).json({ error: "Thiếu groupId hoặc message" });
}
try {
// Dùng Zalo client gửi tin nhắn vào nhóm
await zalo.sendMessage(groupId, message);
res.json({ success: true, message: "Đã gửi" });
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
app.listen(API_PORT, () => {
console.log(`🚀 Zalo Bot Server đang chạy tại port ${API_PORT}`);
});
```
### Bước 3: Hoàn thiện Workflow trên n8n
Sau khi chạy file Node.js lên và có ai đó nhắn tin vào Group Zalo, Node Webhook của n8n sẽ nhận được dữ liệu dạng JSON chứa `groupId``text`. Tiếp theo trên n8n, bạn làm như sau:
1. **Xử lý Logic**: Thêm các Node như **If**, **Switch**, hoặc **OpenAI / Chatbot** để phân tích tin nhắn người dùng gửi (VD: tìm từ khóa, lấy link shopee, lấy thông tin sản phẩm).
2. **Trả lời về Group**: Thêm Node **HTTP Request** ở cuối luồng:
- Method: **POST**
- URL: `http://localhost:3000/api/send` *(Trỏ về server Node.js của bạn)*
- Send Body: Bật ON
- Body Parameters:
- `groupId`: Kéo mapping từ ID group của Node Webhook.
- `message`: Câu trả lời mà n8n vừa tự động tạo ra.
> [!WARNING]
> **Rủi ro khóa tài khoản (Ban Account):** Zalo rất khắt khe với các bot cá nhân. Nếu bot gửi quá nhiều tin nhắn, gửi liên tục hoặc spam link, tài khoản clone của bạn có thể bị khóa.
> **Mẹo:** Trong n8n, bạn nên dùng Node `Wait` (đợi 2-5 giây) trước khi thực hiện HTTP Request trả lời để mô phỏng giống người thật đang gõ phím.
> [!TIP]
> Khi chuyển lên chạy thực tế (Production), hãy nhớ đổi đường dẫn Webhook của n8n từ `webhook-test/...` sang `webhook/...`.
### Mẹo nâng cao: Chỉ định Bot lắng nghe từ một Group Zalo cụ thể
Nếu bạn dùng Node **Zalo Message Trigger** trên n8n và chọn Event Types là *Group Messages*, nó sẽ mặc định lắng nghe toàn bộ các nhóm mà tài khoản đó tham gia. Để giới hạn bot chỉ hoạt động trong một Group cụ thể (Ví dụ ID: `1435552796197334408`), bạn cần thêm một bước lọc ngay sau Node Trigger.
#### Cách 1: Sử dụng Node "Filter" (Trên n8n bản mới)
1. Thêm một Node **Filter** ngay sau Node **Zalo Message Trigger**.
2. Trong cài đặt của Node Filter, ở phần **Conditions** (Điều kiện), bạn thiết lập như sau:
- **Value 1**: Kéo thả trường ID của group từ dữ liệu Output của Zalo Trigger (Thường tên là `groupId`, `threadId`, hoặc `id` nằm trong `$json`). Biểu thức sẽ là: `{{ $json.groupId }}`.
- **Condition**: Chọn `Equals` (hoặc `String Equals`).
- **Value 2**: Điền ID của group vào: `1435552796197334408`
3. Lưu lại. Các Node tiếp theo (tạo câu trả lời, gửi lại tin nhắn...) bạn nối vào cổng **Output (Kept)** của Node Filter.
#### Cách 2: Sử dụng Node "If" (Trên n8n bản cũ)
1. Thêm Node **If** vào sau **Zalo Message Trigger**.
2. Thêm một điều kiện (Add Condition) dạng **String** hoặc **Number**:
- **Value 1**: `{{ $json.groupId }}`
- **Operation**: `Equal`
- **Value 2**: `1435552796197334408`
3. Nối các thao tác xử lý tiếp theo vào cổng **True** của Node If. Cổng **False** bỏ trống để bot "im lặng" khi nhận tin nhắn từ group khác.
---
### Kịch bản: Kiểm tra Link Shopee và Trả lời (Tag tên người gửi)
Để bot nhận dạng tin nhắn có chứa link Shopee không, sau đó phản hồi lại group và tag thẳng tên người gửi, hãy làm tiếp các bước sau trên **n8n**:
#### Bước 1: Dùng Node "If" để tìm link Shopee
Nối tiếp sau Node lọc ID Group ở trên, bạn thêm một Node **If** (hoặc **Filter**) để kiểm tra nội dung tin nhắn.
1. Thêm Condition dạng **String**.
2. **Value 1**: `{{ $json.text }}` *(Trường chứa nội dung tin nhắn người dùng gửi, tùy theo cấu trúc dữ liệu của bạn).*
3. **Operation**: Bạn có thể chọn `Contains` (nếu kiểm tra đơn giản) hoặc `Regex Match` (nếu muốn độ chính xác cao).
- Nếu dùng **Contains**: Điền Value 2 là `shopee.vn` (Có thể thêm điều kiện `OR` để kiểm tra thêm `shp.ee`).
- Nếu dùng **Regex Match**: Điền Value 2 là `https?:\/\/[a-zA-Z0-9.-]*(shopee\.vn|shp\.ee)\/[^\s]+`
4. Nếu hợp lệ, luồng sẽ đi qua cổng **True**.
#### Bước 2: Tạo nội dung phản hồi
Thêm một Node **Set** (hoặc **Edit Fields**) vào nhánh True. Ta sẽ tạo ra câu trả lời có chứa tag.
- Thêm trường `replyMessage` kiểu String.
- Nội dung biểu thức: `Chào @{{ $json.senderName }}, link Shopee của bạn hợp lệ!`
*(Lưu ý: Hãy lấy đúng trường tên người gửi thay cho `$json.senderName` từ Node Trigger ban đầu).*
#### Bước 3: Gửi HTTP Request phản hồi (Có dữ liệu Mentions)
Tính năng Tag (Mention) của Zalo thường yêu cầu gửi kèm ID của người dùng. Trong Node **HTTP Request** cuối cùng (Node gọi về server Node.js), bạn cấu hình JSON Body như sau:
```json
{
"groupId": "{{ $json.groupId }}",
"message": "{{ $node["Set"].json["replyMessage"] }}",
"mentions": [
{
"id": "{{ $json.senderId }}"
}
]
}
```
*(Lưu ý: Mã nguồn Node.js trung gian của bạn cũng cần được cập nhật để đọc mảng `mentions` này và đưa vào hàm gửi tin nhắn của thư viện Zalo API mà bạn đang dùng).*
+65
View File
@@ -0,0 +1,65 @@
class BotController {
constructor(zaloClient) {
// zaloClient là instance của thư viện zalo (ví dụ: zca-js) đã đăng nhập thành công
this.zalo = zaloClient;
}
/**
* API Endpoint: Nhận yêu cầu từ n8n và gửi tin nhắn vào Group Zalo
*/
async sendMessage(req, res) {
try {
const { groupId, message, mentions } = req.body;
if (!groupId || !message) {
return res.status(400).json({
success: false,
error: "Vui lòng cung cấp đủ groupId và message"
});
}
// Xử lý Mentions để bôi xanh tên người dùng (Zalo API yêu cầu vị trí và độ dài)
let formattedMentions = [];
if (mentions && Array.isArray(mentions)) {
mentions.forEach(mention => {
// Nếu n8n gửi lên biến name (VD: "@Hiển Phan")
if (mention.name) {
// Tìm vị trí chữ "@Hiển Phan" trong câu chat
const pos = message.indexOf(mention.name);
if (pos !== -1) {
formattedMentions.push({
uid: mention.id, // ID người dùng
pos: pos, // Vị trí bắt đầu
len: mention.name.length // Số ký tự cần bôi xanh
});
}
} else {
// Nếu không có tên, đành truyền ID chay
formattedMentions.push({ uid: mention.id, pos: 0, len: 0 });
}
});
}
// Thực hiện gửi tin nhắn qua thư viện Zalo
// Cú pháp của zca-js: sendMessage(groupId, message, quote, mentions)
const result = await this.zalo.sendMessage(groupId, message, null, formattedMentions);
console.log(`[Zalo Bot] Đã gửi tin nhắn vào group ${groupId} thành công!`);
return res.json({
success: true,
message: "Đã gửi tin nhắn",
mentions_processed: formattedMentions,
data: result
});
} catch (error) {
console.error("[Zalo Bot] Lỗi khi gửi tin nhắn:", error);
return res.status(500).json({ success: false, error: error.message });
}
}
}
module.exports = BotController;