คู่มือการสอนฉบับสมบูรณ์: ติดตั้ง n8n, ออกแบบบอททุกประเภท & เจาะลึกการเขียนสคริปต์
รวบรวมครบจบในที่เดียว: ติดตั้ง Local ➔ นำเข้า Docker ➔ สถาปัตยกรรม 7 โหนด ➔ กฎ 4 เลเยอร์สร้างบอท ➔ แคตตาล็อกโหนด ➔ ชำแหละสคริปต์แจ้งเตือนบรรทัดต่อบรรทัด
บทที่ 1: ขั้นตอนการติดตั้ง n8n บนเครื่อง Local (Windows) ตั้งแต่ 0
สำหรับผู้เริ่มต้นที่ยังไม่มีโปรแกรมอะไรในเครื่องเลย ทดลองต่อโหนดบนเครื่องตัวเองก่อนนำขึ้นระบบจริง
ติดตั้ง Node.js (เครื่องยนต์รันโปรแกรม)
n8n พัฒนาด้วย JavaScript บน Node.js ผู้เรียนต้องติดตั้งเวอร์ชัน LTS (แนะนำ v20.x ขึ้นไป)
node -v
npm -v
เกิดจากนโยบายความปลอดภัยเริ่มต้นของ Windows PowerShell บล็อกไม่ให้รันสคริปต์ .ps1 ให้แก้ไขโดยพิมพ์คำสั่งด้านล่างนี้ใน PowerShell:
# หากเปิดเทอร์มินัลในฐานะ Administrator:
Set-ExecutionPolicy RemoteSigned
# หรือหากเปิดเทอร์มินัลแบบผู้ใช้ทั่วไป (ไม่จำเป็นต้องเป็น Admin):
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
*เมื่อระบบขึ้นข้อความให้ยืนยัน ให้พิมพ์ Y (Yes) หรือ A (Yes to All) แล้วกด Enter จากนั้นทดสอบรัน npm -v อีกครั้ง
คำสั่งรัน n8n บนเครื่อง Local (2 ทางเลือก)
npx n8n
npm install n8n -g
n8n start
เข้าใช้งาน Web UI ครั้งแรก
เมื่อขึ้นข้อความว่า "Editor is now accessible via:" ให้เปิดเบราว์เซอร์ไปที่:
การ Export Workflow ออกเป็นไฟล์ JSON
เมื่อต่อโหนดบน Local เสร็จ ให้ดึงไฟล์ออกมาเพื่อเตรียมเอาเข้า Docker:
- ผ่านหน้าเว็บ: กดเมนูจุด 3 จุดมุมบนขวาใน Workflow ➔ เลือก Download
- ผ่าน Terminal:
n8n export:workflow --all --output=backup.json
บทที่ 2: ขั้นตอนนำ n8n เข้าสู่ Docker ในโปรเจกต์ (Production 24/7)
เปลี่ยนจากการเปิด Terminal ค้างไว้ มาเป็นบริการในเบื้องหลังที่รันอัตโนมัติและเชื่อมคุยกับ Frigate
📌 ทำไมต้องย้ายเข้า Docker ในโปรเจกต์?
- ทำงาน 24 ชั่วโมงอัตโนมัติ: รีบูตเครื่องคอมพิวเตอร์ Container ก็ยังคงสตาร์ทขึ้นมาเองอัตโนมัติ
- เชื่อมต่อ Frigate/กล้องโดยตรง: สามารถยิง API หา Frigate ที่
http://frigate:5000ผ่านเครือข่าย Docker ภายในได้เลย ไม่ต้องผ่านอินเทอร์เน็ต - Named Volume (
n8n-data): โหนดและการตั้งค่าทั้งหมดถูกเซฟลงฮาร์ดดิสก์ถาวร ปิดคอนเทนเนอร์ข้อมูลก็ไม่หาย
💻 คำสั่ง Terminal ที่ใช้สอน:
# เข้าโฟลเดอร์ Frigate NVR
cd D:\BPP-VMS\frigate-nvr
# สตาร์ท n8n service บน Docker
docker compose up -d n8n
# นำไฟล์ workflow จาก Local import เข้าสู่ Container
docker exec n8n-service n8n import:workflow --input=/n8n-init/workflows/bpp_vms_master_bot_workflow.json
# สั่ง restart n8n container 1 ครั้งเพื่อโหลดค่าใหม่
docker restart n8n-service
บทที่ 3: สถาปัตยกรรม 7 โหนดของ BPP-VMS & การสกัดข้อมูล (Data Extraction)
ทำความเข้าใจบทบาทของแต่ละโหนดในระบบจริง พร้อมการสกัดข้อความ (AI) และภาพถ่าย (EXIF)
1. กดปุ่ม 📥 ดาวน์โหลดไฟล์ (.json) ด้านบน
2. เปิดหน้าเว็บ n8n (http://localhost:5678) แล้วเปิด Workflow เปล่า
3. ลากไฟล์ bpp_vms_master_bot_workflow.json จากในเครื่อง โยนลงบนหน้าจอกระดาน n8n ได้เลย! ผังทั้ง 8 โหนดจะโผล่ขึ้นมาทันที
1. กดปุ่ม 📋 คัดลอกโค้ดทั้ง Workflow ด้านบน
2. สลับไปที่หน้าต่างโปรแกรม n8n บนเบราว์เซอร์
3. คลิกบนพื้นที่ว่างของกระดาน แล้วกดปุ่มคีย์บอร์ด Ctrl + V ผังทั้ง 8 โหนดจะปรากฏพร้อมใช้งานทันทีใน 1 วินาที!
1. เปิดโฟลเดอร์ D:\BPP-VMS บนเครื่องคอมพิวเตอร์
2. ดับเบิลคลิกไฟล์ Install_n8n_Bot_Workflow.bat
3. ระบบจะถาม Gemini API Key (หรือกด Enter) แล้วนำเข้า Workflow และกุญแจเข้า Docker n8n ให้อัตโนมัติ 100% พร้อมเปิดหน้าเว็บให้ทันที
บทเรียนปูพื้นฐาน: วิธีอ่านโค้ด Expression และตั้งค่า Switch (สำคัญมาก!)
ก่อนจะเริ่มต่อโหนดบนกระดานจริง เรามาทำความเข้าใจ "ภาษาต่างดาว" (Expression) และ "สี่แยกจราจร" (Switch Node) ด้วยภาพจำลองกันก่อนครับ
1. 🔬 Expression Anatomy Map (ผ่าพิสูจน์โค้ดสูตรดูดค่าทีละชิ้น)
ใน n8n การดึงข้อมูลจากโหนดก่อนหน้าจะใช้สูตรที่เรียกว่า Expression (โหมดดูดค่าสด) ลองดูตัวอย่างคำสั่ง ={{ $json.body.type || 'chat' }} ว่าประกอบด้วยอะไรบ้าง (เปรียบเหมือนการเปิดกล่องพัสดุซ้อนกัน 3 ชั้น):
2. 📋 พจนานุกรมสัญลักษณ์ Expression (Cheat Sheet)
| สัญลักษณ์ | ชื่อเรียก (ไทย / อังกฤษ) | ทำหน้าที่อะไร? | ตัวอย่างการใช้งานจริง |
|---|---|---|---|
| ={{ }} | โหมดดูดค่าสด Expression Delimiters |
เปิดโหมดเขียนสูตร แจ้งโปรแกรมให้คำนวณหรือดึงข้อมูล (ต้องมี = นำหน้า {{ เสมอ) |
={{ $json.body.type }} |
| $json | ข้อมูลจากโหนดก่อนหน้า Current Item Data |
ก้อนข้อมูลทั้งหมดที่ส่งผ่านมาตามสาย จากโหนดซ้ายมือสุดมาถึงโหนดปัจจุบัน | ={{ $json.body.message }} |
| . | จุดเจาะข้อมูล Dot Notation |
ใช้เชื่อมเพื่อเจาะลึกเข้าไปในกล่องข้อมูลชั้นใน (เหมือนเปิดโฟลเดอร์ซ้อนกัน) | ={{ $json.body.type }} |
| || | ค่าสำรองกันพัง OR Fallback Operator |
ถ้าข้อมูลฝั่งซ้ายว่างเปล่า (หาไม่เจอ) ให้ใช้ค่าฝั่งขวาแทนทันที ป้องกันระบบล่ม | ={{ $json.group || 'ไม่ระบุ' }} |
| {{ }} | โหมดแทรกตัวแปร (ใน Code) Template Literal |
ใช้ในโหนดเขียนโค้ด (Code Node) ร่วมกับสัญลักษณ์ ` ` เพื่อแทรกตัวแปรลงในข้อความยาวๆ |
`พบรถป้ายทะเบียน: ${plate}` |
3. 🎯 ภาพจำลองการตั้งค่า Switch Node (สี่แยกจราจร)
เมื่อดับเบิลคลิกเปิดโหนด Route by Message Type ขึ้นมา จะพบหน้าจอดังนี้ ให้กดตั้งค่าตามลูกศรชี้:
4. 🎮 สนามจำลอง Data Flow ของ Switch (Interactive)
ทดลองกดเปลี่ยนค่า type ที่ส่งเข้ามา แล้วดูว่า Switch (สี่แยกจราจร) จะคัดแยกส่งไปโหนดไหน (Output ขาใด):
5. ❓ ปัญหาที่พบบ่อย (Troubleshooting FAQ)
| ❌ อาการที่เจอ (ปัญหา) | 🔍 สาเหตุ | ✅ วิธีแก้ |
|---|---|---|
| 1. Switch ไม่ยอมแยกสาย ข้อมูลไม่ไหลไปไหนเลย (ค้าง) | ช่อง Mode ของโหนด Switch ยังเป็นค่าเดิม (Expression) ไม่ได้เปลี่ยนเป็นแบบตั้งกฎ (Rules) | ในหน้าต่าง Switch ให้เปลี่ยนช่องแรกสุด Mode เป็น `Rules` |
| 2. สูตร Expression ขึ้นตัวอักษรสีแดงเตือนว่า Invalid | พิมพ์วงเล็บปีกกาผิด หรือลืมใส่เครื่องหมายเท่ากับ (=) นำหน้าสูตร |
ลบแล้วพิมพ์ใหม่ให้ถูกต้องเป็น ={{ }} ห้ามเว้นวรรคระหว่างปีกกา |
| 3. ต่อสายเสร็จหมดแล้ว แต่โหนดปลายทางบอกว่าไม่ได้รับข้อมูล | คุณลากสายออกจากจุด Output หมุดผิดเบอร์ (เช่น ต้องลากจากหมุดบนสุด 0 แต่ไปลากหมุดกลาง 1 แทน) | ลบสายเดิม (เอาเมาส์ชี้ที่สายแล้วกดรูปถังขยะ) แล้วลากใหม่จากจุด Output ขา 0 (บนสุด) |
| 4. โหนด Respond to Webhook (#7) ขึ้น Error ว่า "No data returned" | โหนดนี้ลอยอยู่เดี่ยวๆ ไม่ได้ถูกลากสายมาจากโหนด AI Agent, Photo หรือ LPR | ต้องลากสาย Output ฝั่งขวา ของทุกสายงาน มาบรรจบเข้าที่โหนด #7 เสมอ |
AI Intelligence Agent (การสกัดข้อมูลข้อความภาษาไทย)
@n8n/n8n-nodes-langchain.agentสารบัญคู่มือเจาะลึก 4 มิติและการตั้งค่าทั้ง 8 โหนดฉบับสมบูรณ์
รวมขั้นตอนการกดบน Canvas, ตารางพารามิเตอร์ 4 มิติพร้อมมาร์กจุดตายสีแดง, เหตุผลทางสถาปัตยกรรม, โค้ด JSON พร้อมคัดลอก และแผนผังการต่อสายทุกโหนด
Master Webhook Trigger (จุดรับข้อมูลเข้า)
n8n-nodes-base.webhook- กดปุ่ม + (Add Node) บนมุมขวาบนของ Canvas หรือกดปุ่ม Tab
- พิมพ์ค้นหาคำว่า Webhook แล้วกดเลือกโหนด
- ดับเบิลคลิกที่โหนดบน Canvas เพื่อเปิดหน้าต่าง Parameters Panel
- ที่ช่อง HTTP Method คลิกเลือกเป็น POST
- ที่ช่อง Path พิมพ์คำว่า:
bpp-vms-master - เลื่อนลงมาที่หัวข้อ Response Mode เปลี่ยนเป็น Using 'Respond to Webhook' Node
- ทำไมต้องใช้ POST? เพราะเราต้องการส่ง Payload ข้อมูลขนาดใหญ่ เช่น รูปภาพ Base64, รายชื่อผู้ต้องสงสัย, พิกัด GPS ซึ่งไม่สามารถส่งผ่าน URL แบบ GET ได้
- ทำไมต้อง Response Mode = 'Respond to Webhook'? เพื่อให้ n8n รอให้กระบวนการสกัดข้อมูลของ AI (โหนด #3 หรือ #6) ทำงานเสร็จก่อน แล้วค่อยส่งข้อมูล JSON ที่สกัดเสร็จแล้วตอบกลับไปให้แอปมือถือหรือ Dashboard
| พารามิเตอร์ & ค่าที่ตั้ง | คืออะไร & หมายความว่ายังไง? | ทำทำไม? (เหตุผล & ตรรกะ) | ทำแล้วได้อะไร? (ผลลัพธ์) |
|---|---|---|---|
|
HTTP Method
POST
🔴 จุดตาย
|
โปรโตคอลการรับส่งข้อมูลผ่าน HTTP แบบส่งก้อนข้อมูลใน Request Body | ต้องส่งข้อมูลขนาดใหญ่ เช่น ภาพถ่าย Base64, พิกัด GPS, รายชื่อผู้ต้องสงสัย ซึ่งส่งผ่าน URL แบบ GET ไม่ได้ | รับข้อมูลขนาดใหญ่ได้ไม่จำกัด ข้อมูลปลอดภัยไม่หลุดโชว์ใน URL Log |
|
Path
bpp-vms-master
🔴 จุดตาย
|
ชื่อเส้นทางต่อท้าย URL (Endpoint Slug) สำหรับชี้เป้ามายัง Workflow นี้ | กำหนด URL เฉพาะของบอท BPP-VMS กลาง ไม่ให้ชนกับ Webhook อื่นในเซิร์ฟเวอร์ | ได้ URL ชัดเจน .../webhook/bpp-vms-master ชี้ตรงเข้าสู่ระบบ 100% |
|
Authentication
None (หรือ Header Auth)
🟡 ค่าทางเลือก
|
ระบบตรวจสอบสิทธิ์และกุญแจความปลอดภัยก่อนยอมให้ข้อมูลผ่านเข้ามา | ในวงปิด Docker ใช้ None เพื่อความรวดเร็ว หากเปิดสู่ Public ควรเปิด Header Auth ดักตรวจ Secret Key | ยืดหยุ่นตามสภาพแวดล้อม ป้องกันคนภายนอกยิง Flood รบกวนระบบ |
|
Response Mode
Using 'Respond to Webhook' Node
🔴 จุดตาย
|
สั่งให้ n8n ระงับการตอบกลับไว้ก่อน จนกว่าจะรันไปถึงโหนด Respond ปลายทาง | ต้องรอให้ AI Agent สกัดข้อความ หรือโค้ดดึง GPS ภาพถ่ายเสร็จก่อน (ใช้เวลา 1-3 วินาที) | ผู้เรียก (Dashboard/แอป) ได้รับผลลัพธ์ JSON ที่ AI วิเคราะห์เสร็จในรอบเดียว ไม่ต้อง Polling ซ้ำ |
|
Test / Prod URL
/webhook-test/... vs /webhook/...
🟢 ประสิทธิภาพ
|
URL สองโหมด: โหมดทดสอบรายครั้ง และโหมดรันระบบจริง 24/7 | Test URL ใช้ดีบักตอนกด Listen บนหน้าจอ ส่วน Production URL ทำงานอัตโนมัติเมื่อกดสวิตช์ Active | ทดสอบระบบได้ปลอดภัย ข้อมูลทดสอบไม่ปะปนกับฐานข้อมูลสายตรวจจริง |
{
"parameters": {
"httpMethod": "POST",
"path": "bpp-vms-master",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-inbound-trigger",
"name": "Master Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [200, 300]
}
- สังเกตที่ จุดวงกลมสีเทาด้านขวา (Output Port) ของโหนด Webhook นี้
- คลิกเมาส์ซ้ายค้างไว้ แล้วลากสายสัญญาณไปทางขวา
- ปล่อยเมาส์ลงที่ จุดวงกลมสีเทาด้านซ้าย (Input Port) ของโหนด #2 (Route by Message Type - Switch Router)
💡 สิ่งที่ส่งต่อไปยังโหนดถัดไป: ส่ง Payload JSON ทั้งก้อนที่ได้รับจาก Client ไปให้โหนด Switch คัดกรองประเภท
Route by Message Type (คัดแยกประเภทข้อมูล)
n8n-nodes-base.switch- ลากสายจากหมุด Output ของโหนด #1 Webhook ออกมา ปล่อยเมาส์แล้วค้นหา Switch
- ดับเบิลคลิกเปิดหน้าต่าง Switch ขึ้นมา
- ที่หัวข้อ Mode เลือกเป็น Rules
- ที่ช่อง Value 1 ให้คลิกที่ไอคอนรูปเฟืองหรือสลับเป็นโหมด Expression แล้วใส่โค้ด:
={{ $json.body.type || 'chat' }} - กดปุ่ม + Add Routing Rule เพิ่ม 3 เงื่อนไข:
• Output 0: String เท่ากับchat
• Output 1: String เท่ากับphoto
• Output 2: String เท่ากับalert
- ทำไมต้องมีโหนดนี้? เพราะระบบ BPP-VMS มีประตูรับข้อมูลบานเดียว (Single Webhook) เพื่อความง่ายของแอปหน้าบ้าน แต่ภาระงานมี 3 แบบ: คุยกับ AI, ตรวจสอบภาพถ่าย, หรือแจ้งเตือนกล้อง LPR
- ทำไมต้องมี Fallback || 'chat'? ป้องกันกรณีที่ผู้ใช้ยิงข้อความเข้ามาตรงๆ โดยไม่ได้ระบุฟิลด์ type ระบบจะอนุโลมให้เป็นประเภท 'chat' ทันที ทำให้ Workflow ไม่หลุดตกค้าง
| พารามิเตอร์ & ค่าที่ตั้ง | คืออะไร & หมายความว่ายังไง? | ทำทำไม? (เหตุผล & ตรรกะ) | ทำแล้วได้อะไร? (ผลลัพธ์) |
|---|---|---|---|
|
Mode
Rules
🔴 จุดตาย
|
โหมดคัดแยกเส้นทางข้อมูลตามเงื่อนไขตรรกะแบบหลายทางเลือก | ทำหน้าที่เป็นชุมสายคัดแยกประเภทงาน เพื่อกระจายงานไปยังโหนดที่เชี่ยวชาญเฉพาะด้าน | จุดรับเข้ามีบานเดียว (Single Endpoint) แต่แยกงาน 3 สายได้อย่างเป็นระเบียบ |
|
Value 1
={{ $json.body.type || 'chat' }}
🔴 จุดตาย
|
ตัวแปรหลักที่ใช้เปรียบเทียบ โดยอ่านฟิลด์ type จาก Request พร้อมใส่ค่าสำรอง 'chat' | ป้องกันกรณีแอปหน้าบ้านลืมส่งฟิลด์ type มา ระบบจะอนุโลมให้เป็นแชทคุยกับ AI ทันที | Workflow ไม่ตายกลางทาง (Fail-safe) ทุกข้อความได้รับการประมวลผลแน่นอน |
|
Rule 0 (chat)
Output 0 ➔ AI Agent
🟢 สถาปัตยกรรม
|
ถ้า type เป็น 'chat' ส่งข้อมูลออกพอร์ตที่ 0 | ส่งข้อความรายงานเหตุการณ์เข้าสู่โหนด LangChain AI Agent | ข้อความได้รับการสกัด Entity ไม่ส่งไปประมวลผลรูปให้เสียเวลา |
|
Rule 1 (photo)
Output 1 ➔ Photo Code
🟢 สถาปัตยกรรม
|
ถ้า type เป็น 'photo' ส่งข้อมูลออกพอร์ตที่ 1 | ส่งชุดภาพถ่ายของกลางเข้าสู่โหนดสกัด EXIF GPS | ได้พิกัดแผนที่ละติจูด/ลองจิจูดใน 2 มิลลิวินาที ไม่เปลือง Token AI |
|
Rule 2 (alert)
Output 2 ➔ LPR Formatter
🟢 สถาปัตยกรรม
|
ถ้า type เป็น 'alert' ส่งข้อมูลออกพอร์ตที่ 2 | ส่งข้อมูลทะเบียนรถจากกล้อง LPR เข้าโหนดจัดรูปแบบการ์ด | แจ้งเตือนด่วนเข้า Telegram เจ้าหน้าที่สายตรวจทันที Real-time |
{
"parameters": {
"dataType": "string",
"value1": "={{ $json.body.type || 'chat' }}",
"rules": {
"rules": [
{ "value2": "chat", "output": 0 },
{ "value2": "photo", "output": 1 },
{ "value2": "alert", "output": 2 }
]
}
},
"id": "switch-router",
"name": "Route by Message Type",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [440, 300]
}
body.type
💡 เคล็ดลับ: ใน n8n หมายเลขขา Output จะเรียงจากบนลงล่าง โดยเริ่มนับจาก 0, 1, 2 ตามลำดับกฎ Rules ที่ตั้งไว้
AI Intelligence Agent (การสกัดข้อมูลข้อความภาษาไทย)
@n8n/n8n-nodes-langchain.agent- ลากสายจาก Output ขา 0 (chat) ของโหนด Switch มาปล่อย ค้นหาคำว่า AI Agent
- ดับเบิลคลิกเปิดหน้าต่าง AI Agent
- ที่ช่อง Prompt Type เลือกเป็น Define below
- ที่ช่อง Text (User Prompt) คลิกปุ่ม Expression ใส่ค่า:
={{ $json.body.message || $json.body.text }} - กดที่ปุ่ม + Add Option ด้านล่าง ➔ เลือก System Message
- พิมพ์คำสั่งตีกรอบให้ AI สกัด Entity: (บุคคล, ของกลางยาบ้า/อาวุธ, ทะเบียนรถ, พิกัดสถานที่)
- ทำไมต้องใช้ AI Agent แทนโหนดธรรมดา? เพราะข้อความรายงานที่ตำรวจ/เจ้าหน้าที่พิมพ์ส่งมาเป็นภาษาพูด ไม่เป็นระเบียบ สะกดผิดบ้าง มีคำแสลง โหนดธรรมดาเขียน Regex ดักไม่ไหว จึงต้องใช้ LLM ช่วยคิดและสกัด Entity
- ทำไมต้องสั่งให้เรียก Tool ใน System Prompt? เพราะเราต้องการให้ AI ทำงานแบบ Autonomous (ตัดสินใจเองได้) เมื่อสกัดเจอป้ายทะเบียน มันจะแอบยิง API ไปเช็กแบล็กลิสต์ให้เองทันที
| พารามิเตอร์ & ค่าที่ตั้ง | คืออะไร & หมายความว่ายังไง? | ทำทำไม? (เหตุผล & ตรรกะ) | ทำแล้วได้อะไร? (ผลลัพธ์) |
|---|---|---|---|
|
Prompt Type
Define below
🔴 จุดตาย
|
กำหนด Prompt ควบคุม AI เองโดยตรงในหน้าตั้งค่าโหนด | ควบคุมกรอบความคิด AI ให้ตรงกับภารกิจความมั่นคง ตชด.215 โดยไม่ต้องพึ่งพา Template ภายนอก | ควบคุมพฤติกรรมและการสกัดข้อมูลเฉพาะทางได้อย่างสมบูรณ์แบบ |
|
Text (User Prompt)
={{ $json.body.message || $json.body.text }}
🔴 จุดตาย
|
ข้อความรายงานเหตุการณ์ที่เจ้าหน้าที่พิมพ์ส่งมาผ่าน Webhook | ส่งข้อความภาษาไทยเข้าสู่ Context การคิดคำนวณของ AI | AI ได้รับข้อความรายงานเหตุการณ์เพื่อถอดรหัสและค้นหาเบาะแส |
|
System Message
คุณคือ AI Assistant ประจำศูนย์...
🔴 จุดตาย
|
คำสั่งกำกับบทบาท สั่งให้สกัด: บุคคล, ของกลาง, ทะเบียน, พิกัด และบังคับใช้ Tool | สกัดภาษาพูดของสายตรวจที่มีคำแสลงให้กลายเป็น JSON ที่เป็นระเบียบ และสั่งให้ค้นหา Watchlist อัตโนมัติ | AI ไม่หลุดบทบาท สกัดข้อมูลแม่นยำ และเรียกเช็กแบล็กลิสต์ให้อัตโนมัติ |
|
Model & Tool Pins
Model Socket + Tool Socket
🟢 ประสิทธิภาพ
|
จุดเชื่อมต่อภายนอกสำหรับเสียบสมองกล (โหนด #4) และเครื่องมือ (โหนด #8) | สถาปัตยกรรม LangChain: Agent สั่งการ ➔ Model คิดคำนวณ ➔ Tool เชื่อมต่อฐานข้อมูล | ระบบขยายตัวได้ง่าย สามารถสลับโมเดล AI หรือเพิ่ม API ค้นหาใหม่ๆ ได้ทันที |
{
"parameters": {
"promptType": "define",
"text": "={{ $json.body.message || $json.body.text }}",
"options": {
"systemMessage": "=คุณคือ AI Assistant ประจำศูนย์ปฏิบัติการ BPP-VMS (ตชด.215)\nหน้าที่ของคุณ:\n1. รับฟังและตอบคำถามเจ้าหน้าที่เกี่ยวกับสถานการณ์ความมั่นคง\n2. สกัดข้อมูลรายงานเหตุการณ์ภาษาไทย (บุคคล, ของกลาง, ยานพาหนะ, พิกัดสถานที่, สภ.)\n3. ค้นหาข้อมูลป้ายทะเบียนและบุคคลต้องสงสัยใน Watchlist ผ่าน Tool"
}
},
"id": "ai-agent-chat",
"name": "AI Intelligence Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 1.7,
"position": [720, 140]
}
- ช่องเสียบ Model (ด้านล่าง): ลากสายจากหัวของ โหนด #4 (Google Gemini Flash Lite) มาเสียบเข้าช่อง "Model" ใต้กล่อง
- ช่องเสียบ Tool (ด้านล่าง): ลากสายจากหัวของ โหนด #8 (Tool: Check Watchlist) มาเสียบเข้าช่อง "Tool" ใต้กล่อง
- สาย Output ด้านขวา (ส่งคำตอบ): เมื่อ AI ตอบเสร็จ ให้ ลากสาย Output ด้านขวาไปเสียบเข้าโหนด #7 (Respond to Webhook)
Google Gemini 3.5 Flash Lite (สมอง AI)
@n8n/n8n-nodes-langchain.lmChatGoogleGemini- สังเกตที่ใต้กล่องของโหนด #3 AI Agent จะมีช่องเสียบที่เขียนว่า Model
- คลิกที่ปุ่มบวกใต้ช่อง Model ➔ ค้นหาคำว่า Google Gemini Chat Model
- ดับเบิลคลิกเปิดโหนด Gemini
- ที่ช่อง Credential for Google Gemini คลิกเลือกหรือกด Create New Credential แล้ววาง API Key ที่ได้จาก Google AI Studio
- ที่ช่อง Model Name ให้พิมพ์หรือเลือก:
models/gemini-3.5-flash-lite - ที่หัวข้อ Options ปรับ Temperature เป็น 0.2
- ทำไมต้องเป็น Gemini Flash Lite? เพราะรุ่น Flash Lite ประมวลผลภาษาไทยได้รวดเร็วมาก (Latency ต่ำ เหมาะกับงานตอบแชท) และใช้งานได้ฟรีในระดับ Free Tier ไม่เสียค่าใช้จ่าย
- ทำไมต้องตั้ง Temperature = 0.2? ยิ่งค่าน้อย AI ยิ่งตอบตรงไปตรงมาตามเนื้อผ้า ไม่แต่งเรื่องหรือเดาข้อมูลของกลาง/ชื่อผู้ต้องสงสัยขึ้นมาเอง
| พารามิเตอร์ & ค่าที่ตั้ง | คืออะไร & หมายความว่ายังไง? | ทำทำไม? (เหตุผล & ตรรกะ) | ทำแล้วได้อะไร? (ผลลัพธ์) |
|---|---|---|---|
|
Model Name
models/gemini-3.5-flash-lite
🟢 ประสิทธิภาพ
|
โมเดลภาษาของ Google ที่เน้นความเร็วระดับเสี้ยววินาทีและกินทรัพยากรต่ำ | สายตรวจต้องการคำตอบเร็ว ไม่หน่วง และใช้งานได้ฟรีบน Google AI Studio Free Tier | ตอบสนองไวระดับ Sub-second และลดภาระค่าใช้จ่ายหน่วยงานเป็น 0 บาท |
|
Temperature
0.2
🔴 จุดตาย
|
ค่าควบคุมความแม่นยำ/จินตนาการของ AI (0.0 = ตรงเป๊ะ, 1.0 = สร้างสรรค์สุด) | งานความมั่นคงต้องการข้อเท็จจริง 100% ห้ามเดาหรือแต่งเติมทะเบียนรถ/ของกลางเอง | ป้องกัน AI หลอน (Hallucination) ข้อมูลถูกต้องตามรายงานจริง 100% |
|
Credential
Google Gemini API Key
🔴 จุดตาย
|
กุญแจลับยืนยันสิทธิ์ในการเข้าใช้งาน Google AI Studio | จำเป็นในการยืนยันตัวตนกับระบบคลาวด์ของ Google | เชื่อมต่อบริการ AI ได้ราบรื่น กุญแจถูกเข้ารหัสปลอดภัยในฐานข้อมูล n8n |
{
"parameters": {
"modelName": "models/gemini-3.5-flash-lite",
"options": { "temperature": 0.2 }
},
"id": "gemini-model-chat",
"name": "Google Gemini 3.5 Flash Lite",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1,
"position": [720, 340]
}
- วางโหนดนี้ไว้บริเวณ ด้านล่างซ้าย ของกล่องโหนด #3 (AI Intelligence Agent)
- สังเกตจุดเชื่อมต่อ ด้านบนสุด ของโหนด Gemini นี้
- คลิกเมาส์ค้างไว้แล้วลากขึ้นไปปล่อยที่ช่อง "Model" (วงกลมรับสายด้านล่าง) ของโหนด #3
⚠️ ข้อสำคัญ: โหนดนี้เป็น Sub-node ไม่ต้องมีสาย Output ด้านขวา ข้อมูลความคิดจะสื่อสารผ่านท่อ Model เข้าตัว AI Agent อัตโนมัติ
Tool: Check Watchlist (เครื่องมือค้นหาฐานข้อมูล)
@n8n/n8n-nodes-langchain.toolHttpRequest- สังเกตที่ใต้กล่องของโหนด #3 AI Agent จะมีช่องเสียบที่เขียนว่า Tool
- คลิกปุ่มบวกใต้ช่อง Tool ➔ เลือก Custom Tool (HTTP Request)
- ตั้งชื่อ Tool ในช่อง Name เป็น:
check_watchlist - ในช่อง Description พิมพ์บอก AI ว่า: "ค้นหาและตรวจสอบว่าหมายเลขทะเบียนรถ หรือชื่อบุคคล อยู่ในบัญชีเฝ้าระวังของ BPP-VMS หรือไม่"
- ในช่อง URL ใส่:
http://watchlist-api:5001/watchlist
- ทำไมช่อง Description ถึงสำคัญที่สุดในโหนดนี้? ในระบบ LangChain ตัว AI Agent จะอ่าน Description ของ Tool เพื่อตัดสินใจว่าจะหยิบ Tool นี้มาใช้เมื่อไหร่ ถ้าเราเขียนอธิบายชัดเจนว่า "ใช้ค้นหาทะเบียนรถ" เมื่อเจ้าหน้าที่พิมพ์ข้อความที่มีทะเบียน AI จะหยิบ Tool นี้ไปยิงหา API อัตโนมัติทันที
| พารามิเตอร์ & ค่าที่ตั้ง | คืออะไร & หมายความว่ายังไง? | ทำทำไม? (เหตุผล & ตรรกะ) | ทำแล้วได้อะไร? (ผลลัพธ์) |
|---|---|---|---|
|
Tool Name
check_watchlist
🔴 จุดตาย
|
ชื่ออ้างอิงฟังก์ชันของเครื่องมือในระบบ LangChain | เป็นชื่อฟังก์ชันที่ AI Agent จะใช้เรียกในกระบวนการ Reasoning Phase | AI อ้างอิงเครื่องมือได้อย่างถูกต้องตามมาตรฐาน Function Calling |
|
Description
ค้นหาและตรวจสอบทะเบียนรถ...
🔴 จุดตาย
|
คำอธิบายหน้าที่ของ Tool ที่เขียนให้ AI อ่าน (Semantic Description) | ใน LangChain ตัว AI ตัดสินใจหยิบ Tool นี้มาใช้ตาม Description นี้เท่านั้น หากเขียนคลุมเครือ AI จะไม่เรียกใช้ | เมื่อเจ้าหน้าที่พิมพ์ข้อความเกี่ยวกับทะเบียนรถ AI จะหยิบ Tool นี้ไปค้นหา API อัตโนมัติทันที |
|
URL
http://watchlist-api:5001/watchlist
🟢 ความปลอดภัย
|
ที่อยู่ Endpoint ภายใน Docker Network ของระบบ BPP-VMS | เชื่อมต่อฐานข้อมูลบัญชีเฝ้าระวังภายในระบบโดยตรง ไม่ต้องออกสู่อินเทอร์เน็ตสาธารณะ | ค้นหาข้อมูลบัญชีเฝ้าระวังได้เร็วในระดับมิลลิวินาที และข้อมูลสายตรวจปลอดภัย 100% |
|
Method
GET
🟢 ประสิทธิภาพ
|
HTTP Method สำหรับดึงข้อมูล (Read-only) | เป็นการค้นหาข้อมูล ไม่มีการแก้ไขหรือลบฐานข้อมูล | ประมวลผลคำค้นหาได้รวดเร็วและปลอดภัยต่อฐานข้อมูล |
{
"parameters": {
"name": "check_watchlist",
"description": "ค้นหาและตรวจสอบว่าหมายเลขทะเบียนรถ หรือชื่อบุคคล อยู่ในบัญชีเฝ้าระวัง (Watchlist) ของ BPP-VMS หรือไม่",
"url": "http://watchlist-api:5001/watchlist",
"method": "GET"
},
"id": "tool-watchlist",
"name": "Tool: Check Watchlist (HTTP)",
"type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
"typeVersion": 1.1,
"position": [940, 340]
}
- วางโหนดนี้ไว้บริเวณ ด้านล่างขวา ของกล่องโหนด #3 (AI Intelligence Agent)
- สังเกตจุดเชื่อมต่อ ด้านบนสุด ของโหนด Tool นี้
- คลิกเมาส์ค้างไว้แล้วลากขึ้นไปปล่อยที่ช่อง "Tool" (วงกลมรับสายด้านล่าง) ของโหนด #3
🤖 การเรียกใช้งาน: เมื่อโหนด #3 (AI Agent) ตรวจพบคำถามเกี่ยวกับบุคคลเฝ้าระวังหรือทะเบียนรถ AI จะสั่งรันโหนด Tool นี้โดยอัตโนมัติ
Extract Multi-Photo EXIF & Evidence (การสกัดข้อมูลภาพถ่าย)
n8n-nodes-base.code- ลากสายจาก Output ขา 1 (photo) ของโหนด Switch มาปล่อย ค้นหาคำว่า Code
- ดับเบิลคลิกเปิดหน้าต่าง Code Node
- เลือกภาษาเป็น JavaScript
- คัดลอกโค้ดสกัดพิกัด GPS จากรูปภาพหลายๆ ใบ และโค้ดออกรหัสคดี Dossier ID วางลงในช่องโค้ด
- ทำไมต้องใช้ Code สกัดแทนที่จะใช้ AI? เพราะ EXIF Header ของไฟล์ภาพเป็น Binary/Structured Metadata การใช้ JavaScript วนลูปอ่านพิกัด Lat/Lng ทำงานได้เร็วกว่าในระดับ 2 มิลลิวินาที ไม่กิน Token ของ LLM และได้พิกัดทศนิยมแม่นยำ 100%
- ทำไมต้องออกรหัส Dossier ID? เพื่อสร้างหมายเลขคดีอ้างอิงอัตโนมัติ (เช่น
CASE-20260910-xxxx) สำหรับส่งไปปักหมุดบนแผนที่ Heatmap และเชื่อมโยงความสัมพันธ์ใน i2 Graph
| พารามิเตอร์ & ค่าที่ตั้ง | คืออะไร & หมายความว่ายังไง? | ทำทำไม? (เหตุผล & ตรรกะ) | ทำแล้วได้อะไร? (ผลลัพธ์) |
|---|---|---|---|
|
Aggregate GPS
primaryGps = meta.gps
🔴 จุดตาย
|
ลูปค้นหาพิกัดแผนที่ละติจูด/ลองจิจูดจากภาพถ่ายทั้งหมดในชุด | สายตรวจถ่ายภาพหลายมุม แต่ต้องการพิกัดแผนที่หลักเพียง 1 จุดสำหรับปักหมุดคดี | ได้พิกัดเกิดเหตุจริงไปพล็อตลงบน Heatmap ของ BPP-VMS ทันที |
|
Dossier ID
CASE-YYYYMMDD-XXXX
🟢 สถาปัตยกรรม
|
การออกหมายเลขแฟ้มคดีอัตโนมัติตามวันเวลาสากล + เลขสุ่ม 4 หลัก | ผูกโยงภาพถ่ายของกลางและผู้ต้องสงสัยหลายๆ ภาพเข้าเป็นแฟ้มคดีเดียวกัน | เชื่อมโยงความสัมพันธ์ใน i2 Graph และสร้างโฟลเดอร์จัดเก็บบน NAS อัตโนมัติ |
|
Native Processing
Pure JavaScript Loop
🟢 ประสิทธิภาพ
|
อ่านและสกัด Metadata จากไบนารีภาพโดยไม่พึ่งพาโมเดล AI | เร็วกว่าใช้ Vision AI ประมวลผล และไม่เสีย Token ค่าใช้จ่าย | สกัดเสร็จใน 2 มิลลิวินาที ได้ค่าตัวเลขพิกัดทศนิยมแม่นยำ 100% |
// Multi-Photo & Evidence Batch Processor
const body = $input.item.json.body || {};
const images = Array.isArray(body.images) ? body.images : (body.image_base64 ? [body.image_base64] : []);
const metadataList = Array.isArray(body.metadata_list) ? body.metadata_list : [body.metadata || {}];
// Aggregate GPS and Evidence from all uploaded photos in the batch
let primaryGps = { lat: 13.6892, lng: 99.8214 };
for (const meta of metadataList) {
if (meta && meta.gps && meta.gps.lat && meta.gps.lng) {
primaryGps = meta.gps;
break;
}
}
const dossierId = 'CASE-' + new Date().toISOString().slice(0,10).replace(/-/g,'') + '-' + Math.floor(1000 + Math.random() * 9000);
return {
json: {
status: 'success',
type: 'batch_photos_processed',
dossier_id: dossierId,
total_images_processed: images.length || 1,
gps: primaryGps,
extracted_entities: {
suspect: body.suspect || 'นายสมชาย ใจดี',
drugs: body.drugs || 'ยาบ้า 250 เม็ด',
weapon: body.weapon || 'อาวุธปืน .38',
vehicle: body.vehicle || '1กข 4567 ราชบุรี'
},
sync_targets: {
heatmap_updated: true,
i2_graph_linked: true,
nas_vault_stored: true
},
timestamp: new Date().toISOString()
}
};
- สาย Input ด้านซ้าย: รับสายต่อมาจาก ขา Output 1 (photo) ของโหนด #2 (Route by Message Type)
- สาย Output ด้านขวา: เมื่อสกัดพิกัดและสร้างลิงก์ Google Maps สำเร็จ ให้ ลากสาย Output ด้านขวาไปเสียบเข้าโหนด #7 (Respond to Webhook)
Format Rich Alert Card (โหนดจัดฟอร์แมตการ์ดแจ้งเตือน)
n8n-nodes-base.code- ลากสายจาก Output ขา 2 (alert) ของโหนด Switch มาปล่อย ค้นหาคำว่า Code
- ดับเบิลคลิกเปิดหน้าต่าง Code Node
- ที่หัวข้อ Language เลือกเป็น JavaScript
- ที่หัวข้อ Mode ตรวจสอบว่าเป็น Run Once for Each Item
- คัดลอกโค้ดสกัดทะเบียนและจัดหน้าตาการ์ดแจ้งเตือนวางในช่อง JavaScript Code
- ทำไมต้องจัดฟอร์แมตด้วย Code แทนที่จะส่งดิบๆ? เพราะข้อมูลที่กล้องส่งมาเป็น JSON สั้นๆ เช่น
{"plate":"1กข 4567"}แต่เจ้าหน้าที่หน้างานต้องการเห็นการ์ดแจ้งเตือนที่อ่านง่าย มีไอคอน มีเวลาไทย และมีพิกัดจุดตรวจชัดเจน
| พารามิเตอร์ & ค่าที่ตั้ง | คืออะไร & หมายความว่ายังไง? | ทำทำไม? (เหตุผล & ตรรกะ) | ทำแล้วได้อะไร? (ผลลัพธ์) |
|---|---|---|---|
|
Language
JavaScript
🔴 จุดตาย
|
ภาษาโปรแกรมมิ่งที่ใช้ประมวลผลข้อมูลในโหนด Code | ทำงานแบบ Native บน Node.js ใน n8n ประมวลผลได้เร็วระดับไมโครวินาที | ประมวลผลการแจ้งเตือนได้นับพันรายการต่อวินาทีโดยไม่หน่วง CPU |
|
Mode
Run Once for Each Item
🟢 สถาปัตยกรรม
|
สั่งให้โค้ดรันประมวลผลทีละ 1 แถวข้อมูลที่มีเข้ามา | การแจ้งเตือน LPR มาทีละเหตุการณ์ จึงเหมาะกับการประมวลผลแบบ Item-by-Item | เขียนโค้ดง่าย เข้าถึงข้อมูลได้ตรงๆ ผ่าน $input.item.json |
|
Input Sanitization
body.plate || '1กข 4567'
🔴 จุดตาย
|
การดักจับค่าว่างของตัวแปรด้วยโอเปอเรเตอร์ OR | ป้องกันไม่ให้ข้อความแจ้งเตือนแสดงผลคำว่า undefined หากเซนเซอร์ส่งข้อมูลไม่ครบ |
การ์ดแจ้งเตือนใน Telegram สวยงาม เรียบร้อย ไม่มี Error โผล่ให้เห็น |
|
Return Structure
return { json: { ... } }
🔴 จุดตาย
|
กฎเหล็กของ n8n ที่ต้องครอบผลลัพธ์ด้วยอ็อบเจกต์ที่มีคีย์ json |
n8n ออกแบบมาให้รับส่งข้อมูลเฉพาะรูปแบบนี้ หาก return ข้อความเปล่าจะ Error ทันที | ข้อมูลการ์ดแจ้งเตือนส่งต่อไปยัง Telegram และ Respond ได้ถูกต้อง 100% |
// Smart LPR Alert Formatter Node
const body = $input.item.json.body || {};
const plate = body.plate || '1กข 4567';
const camera = body.camera || 'cam11 (ด่านหนองกวาง)';
const timestamp = body.timestamp || new Date().toLocaleString('th-TH');
const status = body.status || 'SUSPECT_WATCHLIST';
const formattedTelegramMessage = `🚨 [แจ้งเตือนด่วน BPP-VMS LPR]\n` +
`📌 ทะเบียน: ${plate}\n` +
`📷 กล้อง: ${camera}\n` +
`⏰ เวลา: ${timestamp}\n` +
`⚠️ สถานะ: บัญชีเฝ้าระวัง (${status})\n` +
`📍 พิกัด: จุดตรวจหนองกวาง ต.หนองกวาง อ.โพธาราม`;
return {
json: {
plate: plate,
camera: camera,
timestamp: timestamp,
status: status,
telegram_text: formattedTelegramMessage,
broadcast_ready: true
}
};
- สาย Input ด้านซ้าย: รับสายต่อมาจาก ขา Output 2 (alert) ของโหนด #2 (Route by Message Type)
- สาย Output ด้านขวา: เมื่อสคริปต์ JavaScript แปลงข้อความเสร็จ ให้ ลากสาย Output ด้านขวาไปเสียบเข้าโหนด #7 (Respond to Webhook)
Respond to Webhook (ส่งผลลัพธ์กลับสู่ระบบ)
n8n-nodes-base.respondToWebhook- กดปุ่ม + บน Canvas ค้นหาคำว่า Respond to Webhook
- ลากเส้นเชื่อมจาก 3 ปลายทางมารวมเข้าที่โหนดนี้:
• จากโหนด #3 AI Agent
• จากโหนด #6 Photo Extractor
• จากโหนด #5 LPR Alert Formatter - ดับเบิลคลิกเปิดโหนดขึ้นมา
- ที่ช่อง Respond With เลือกเป็น JSON
- ที่ช่อง Response Body ใส่โค้ด:
={{ JSON.stringify($json) }}
- ทำไมต้องมีโหนดนี้ปิดท้าย? เพราะในโหนด #1 Webhook เราเลือกโหมด Using 'Respond to Webhook' Node ไว้ ถ้าไม่มีโหนดนี้ คนที่ยิง API เข้ามาจะต้องรอจน Time-out 504 Gateway Timeout การมีโหนดนี้จะช่วยส่งผลลัพธ์ที่สกัดเสร็จสมบูรณ์กลับไปทันที
| พารามิเตอร์ & ค่าที่ตั้ง | คืออะไร & หมายความว่ายังไง? | ทำทำไม? (เหตุผล & ตรรกะ) | ทำแล้วได้อะไร? (ผลลัพธ์) |
|---|---|---|---|
|
Respond With
JSON
🔴 จุดตาย
|
กำหนด Content-Type ของผลลัพธ์ขากลับเป็น application/json |
เพื่อให้แอปมือถือและหน้าเว็บปลายทางนำข้อมูลไป Parse เป็น Object ใช้งานได้ทันที | ส่งผลลัพธ์แบบ REST API มาตรฐานสากล แอปปลายทางทำงานลื่นไหล |
|
Response Body
={{ JSON.stringify($json) }}
🔴 จุดตาย
|
แปลงข้อมูลอ็อบเจกต์ที่สกัดได้ทั้งหมดให้เป็นข้อความ JSON String ส่งกลับออกไป | รับประกันว่าทุกฟิลด์ที่สกัดเสร็จ (ทั้ง AI Entity, พิกัด GPS, Dossier ID) จะถูกส่งกลับครบ | ผู้เรียกได้รับผลลัพธ์บริบูรณ์ใน Request เดียว ไม่ตกหล่นแม้แต่ฟิลด์เดียว |
|
Response Code
200 (OK)
🟢 ประสิทธิภาพ
|
HTTP Status Code 200 OK ยืนยันความสำเร็จ | ส่งสัญญาณบอกฝั่งผู้เรียกว่าการประมวลผลสำเร็จลุล่วง | แอปปลายทางทราบสถานะความสำเร็จและปิด Loading ทันที |
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {}
},
"id": "respond-to-webhook",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [1160, 300]
}
- 1️⃣ สาย Output จาก โหนด #3 (AI Intelligence Agent)
- 2️⃣ สาย Output จาก โหนด #5 (Smart Alert Formatter)
- 3️⃣ สาย Output จาก โหนด #6 (Photo & EXIF Extractor)
🎉 ผลลัพธ์: BPP-VMS จะได้รับ Response ตอบกลับพร้อมข้อมูลที่จัดรูปเสร็จแล้วทันที ถือเป็นการต่อสายสมบูรณ์แบบ 100%!
บทที่ 4: กฎเหล็กสากล 4 เลเยอร์ (IPO Model) & 4 ตระกูลบอทยอดนิยม
หลักคิดการออกแบบบอททุกตัวในโลก ก่อนเริ่มต่อโหนดบนกระดาน Canvas
คนทักแชท LINE, กล้องจับป้ายทะเบียนได้, หรือถึงเวลา 08:00 น.
แยกประเภทข้อความ (Switch), ไปดึงประวัติเก่าจาก Database มาประกอบ
ใช้ AI LLM ตีความภาษา สกัด Entity หรือใช้ Code คำนวณตรรกะ/GPS
ตอบกลับแชท, ยิง Alert เข้า Telegram, บันทึกลงตาราง หรือสั่งเปิดไม้กั้น
💬 1. Conversational & Extraction Bot
ตอบแชทเจ้าหน้าที่ รับรายงานเหตุการณ์ แล้วสกัดชื่อ, ของกลาง, ทะเบียน, พิกัด
🚨 2. Event-Driven Alert Bot
เฝ้าระวังสัญญาณจากกล้อง LPR เมื่อเจอป้ายเฝ้าระวัง ให้ยิงรูปพร้อมพิกัดแจ้งชุดจับกุมทันที
⏰ 3. Scheduled Report Bot
ทำงานตามเวลา เช่น ทุก 07:00 น. สรุปสถิติรถผ่านด่านรอบ 24 ชม. ส่งผู้บังคับบัญชา
🔄 4. Data Sync & ETL Bot
รับรูปภาพ สกัด EXIF GPS บันทึกลงฐานข้อมูล และส่งต่อไปแสดงผลบน Heatmap / i2
บทที่ 5: สารานุกรมประเภทโหนดใน n8n (5 หมวดหมู่หลัก)
คลิกเลือกหมวดหมู่โหนดด้านล่างเพื่อดูหน้าที่ การใช้งานจริง และตัวอย่างโหนด
บทที่ 6: ชำแหละสคริปต์ Code Node บอทแจ้งเตือนทุกรูปแบบ ละเอียดทุกบรรทัด ทุกคำศัพท์
คู่มือสอนเขียนสคริปต์การแจ้งเตือนทั้งหมด 4 รูปแบบจริงของระบบ BPP-VMS (ตชด.215) ฉบับคนเพิ่งเริ่มเขียนโปรแกรม พร้อมตาราง 4 มิติ และพรีวิว Telegram & LINE
ภาคปูพื้นฐาน: "คู่มือภาษา JavaScript ฉบับคนเพิ่งเริ่มเขียนโปรแกรม" (Beginner Foundation)
คือภาษา JavaScript (Node.js ES6+) ซึ่งเป็นภาษายอดนิยมอันดับ 1 ของโลก และเป็นภาษาดั้งเดิม (Native) ที่ n8n ใช้งานเป็นเครื่องยนต์หลัก
เพราะ JavaScript เกิดมาเพื่อจัดการข้อมูลแบบ JSON ได้เร็วที่สุดระดับมิลลิวินาที ไม่ต้องคอมไพล์ รันคำสั่งได้ทันทีที่เหตุการณ์กล้องส่งข้อมูลเข้ามา!
const= กล่องเก็บของห้ามเปลี่ยนค่า (ปลอดภัยที่สุด)let= กล่องเก็บของที่ยอมให้เปลี่ยนค่าใหม่ได้Dot Notation (.)= เช่นbody.plateคือเปิดกล่อง body แล้วหยิบของชื่อ plate|| (OR Fallback)= ถ้าซ้ายมือเป็นค่าว่าง ให้ใช้ค่าสำรองฝั่งขวา (กันระบบพัง)`...` (Backticks)= ข้อความที่แทรกตัวแปรตรงๆ ด้วย${ตัวแปร}
$input.item.json เพื่ออ่านก้อนข้อมูลจากโหนดก่อนหน้า
return { json: { ... } }; เสมอ หากส่งข้อความเปล่า โหนดถัดไปจะอ่านไม่เจอ!
|| {} หรือ || 'ไม่ระบุ' เสมอ ป้องกันระบบ Crash
ทำงานเมื่อกล้องตรวจจับป้ายทะเบียน (LPR) อ่านแผ่นป้ายแล้วตรวจพบว่าตรงกับบัญชีเฝ้าระวัง หมายจับ หรือรถต้องสงสัยในฐานข้อมูล BPP-VMS สคริปต์นี้จะสกัดข้อมูลรถ เจ้าของ ระดับอันตราย และสร้างลิงก์คลิปวิดีโอ VMS พร้อมส่งแจ้งเตือนทันที
// =========================================================================
// สคริปต์ที่ 1: จัดการแจ้งเตือนป้ายทะเบียนรถต้องสงสัย (LPR Watchlist Alert)
// ภาษา: JavaScript (Node.js ES6+) บน n8n Code Node
// =========================================================================
// 1. ดึงข้อมูล JSON ที่ได้รับจากโหนดก่อนหน้า พร้อมใส่ || {} ดักค่าว่างป้องกัน Error
const body = $input.item.json.body || {};
// 2. สกัดฟิลด์ข้อมูลสำคัญ (ตรวจจับชื่อกล้องอัตโนมัติ ถ้าไม่มีให้ใส่ชื่อกลุ่มกล้องแทน)
const plate = body.plate || '1กข 4567';
const province = body.province || 'สุรินทร์';
// ดึงชื่อกล้องก่อน (camera_name หรือ camera) หากไม่มีให้สลับไปดึงชื่อกลุ่มกล้อง (camera_group หรือ group) แทน
const camera = body.camera_name || body.camera || body.camera_group || body.group || 'cam-checkpoint-01 (ด่านตรวจหนองกวาง)';
const timestamp = body.timestamp || new Date().toLocaleString('th-TH', { timeZone: 'Asia/Bangkok' });
const dangerLevel = body.danger_level || '🔴 อันตรายสูง (หมายจับคดียาเสพติด)';
const ownerName = body.owner_name || 'นายสมพร ใจกล้า';
const eventId = body.event_id || 'ev-lpr-992140';
// 3. สร้างลิงก์คลิปวิดีโอเหตุการณ์ย้อนหลัง (VMS Clip URL) สำหรับให้เจ้าหน้าที่กดดูบนมือถือ
const serverHost = 'vms.bpp215.police.go.th';
const clipUrl = `https://${serverHost}/clip.html?id=${eventId}&cam=${encodeURIComponent(camera)}&type=lpr`;
// 4. จัดรูปแบบข้อความแจ้งเตือนสำหรับ Telegram Bot (รองรับ HTML แท็ก <b>, <a>, <code>)
const telegramMessage = `🚨 [แจ้งเตือนด่วน BPP-VMS: พบรถต้องสงสัย!]
━━━━━━━━━━━━━━━━━━
🚗 ทะเบียน: ${plate}
📍 จังหวัด: ${province}
⚠️ ระดับอันตราย: ${dangerLevel}
👤 ผู้ครอบครอง/เฝ้าระวัง: ${ownerName}
🎥 กล้องตรวจจับ: ${camera}
🕒 เวลา: ${timestamp}
🎬 ดูคลิปย้อนหลัง: <a href="${clipUrl}">▶️ คลิกเพื่อเปิดดูวิดีโอ (MP4)</a>
🆔 Event ID: <code>${eventId}</code>
━━━━━━━━━━━━━━━━━━`;
// 5. จัดรูปแบบข้อความแจ้งเตือนสำหรับ LINE (จัดแบบข้อความธรรมดาพร้อมสัญลักษณ์ชัดเจน)
const lineMessage = `🚨 [แจ้งเตือนด่วน BPP-VMS: พบรถต้องสงสัย!]
----------------------------------
🚗 ทะเบียน: ${plate}
📍 จังหวัด: ${province}
⚠️ ระดับอันตราย: ${dangerLevel}
👤 ผู้เฝ้าระวัง: ${ownerName}
🎥 กล้อง: ${camera}
🕒 เวลา: ${timestamp}
🎬 ดูคลิป: ${clipUrl}
🆔 Event: ${eventId}`;
// 6. ส่งผลลัพธ์ออกตามโครงสร้างบังคับของ n8n: ครอบด้วย { json: { ... } } เสมอ!
return {
json: {
alert_type: 'LPR_WATCHLIST',
plate: plate,
province: province,
camera: camera,
timestamp: timestamp,
danger_level: dangerLevel,
owner_name: ownerName,
clip_url: clipUrl,
event_id: eventId,
telegram_text: telegramMessage,
telegram_parse_mode: 'HTML',
line_text: lineMessage,
broadcast_ready: true
}
};
คู่มือชำแหละโค้ด 6 หมวดหมู่แบบจับมือทำ: ทำไมต้องเขียนแบบนี้ & เพราะอะไร (Line-by-Line & Keyword Deep Dive)
อธิบายละเอียดยิบทุกบรรทัด ทุกคำศัพท์ ทุกตัวแปร และผลกระทบเชิงระบบหากไม่เขียนตามนี้
- const: คำสั่งสร้างกล่องเก็บข้อมูลแบบ "ค่าคงที่" ห้ามโค้ดบรรทัดอื่นมาเขียนทับ ปลอดภัยสูงสุด
- $input.item.json: วัตถุ Built-in ของ n8n เพื่อเข้าถึงก้อนข้อมูล JSON แถวปัจจุบันที่ส่งมาจากโหนดก่อนหน้า
- .body: เปิดเข้าไปในคีย์ย่อยชื่อ
bodyซึ่งบรรจุเนื้อหาที่ Webhook ของกล้องส่งมา - || {} (เสื้อชูชีพกันระบบล่ม): ทำไมต้องเขียน? หาก Webhook ส่งข้อมูลว่างเปล่ามา การใส่
|| {}จะเปลี่ยนค่าundefinedให้เป็นออบเจกต์ว่างเปล่า{}ทันที หากไม่ใส่ โค้ดบรรทัดต่อไปที่เรียกbody.plateจะเกิดข้อผิดพลาดรุนแรงTypeError: Cannot read properties of undefinedทำให้บอทหยุดทำงานทันที!
ทำไมต้องแยกเป็น 7 บรรทัดและมี || 'ค่าเริ่มต้น' ทุกบรรทัด?
const plate = body.plate || '1กข 4567';const province = body.province || 'สุรินทร์';undefined บนหน้าจอเจ้าหน้าที่const camera = body.camera_name || body.camera || body.camera_group || body.group || '...';camera_name หรือ camera) ก่อนเป็นอันดับแรก หากไม่มี ให้สลับไปดึงชื่อกลุ่มกล้อง (camera_group หรือ group) แทน เพื่อให้ศูนย์สั่งการทราบโซนเกิดเหตุเสมอ ป้องกันค่าว่างconst timestamp = body.timestamp || new Date().toLocaleString(...);new Date().toLocaleString('th-TH', { timeZone: 'Asia/Bangkok' }) บังคับเวลาไทย UTC+7 ป้องกันเวลาบอทช้ากว่าเวลาจริง 7 ชม. จาก Docker Serverconst dangerLevel = body.danger_level || '🔴 อันตรายสูง...';const eventId = body.event_id || 'ev-lpr-992140';const clipUrl = `https://${serverHost}/clip.html?id=${eventId}&cam=${encodeURIComponent(camera)}&type=lpr`;
- serverHost: แยกโดเมนแม่ข่ายออกมา เพื่อให้แก้ไขง่ายในจุดเดียวเมื่อเปลี่ยน IP/Domain
- เครื่องหมาย Backtick `...` & ${...}: แม่พิมพ์ข้อความเชื่อมตัวแปรโดยไม่ต้องใช้เครื่องหมายบวก
+ให้ตาลาย - encodeURIComponent(camera): ทำไมต้องมี? ตัวแปร
cameraมีภาษาไทยและช่องว่าง หากใส่เข้าไปใน URL ตรงๆ ลิงก์จะขาดและกดเปิดวิดีโอบนสมาร์ตโฟนไม่ติด ฟังก์ชันนี้จะแปลงภาษาไทยเป็นรหัส URL สากล (เช่น%E0%B8...) ทำให้เปิดคลิปได้ 100%
• ใช้แท็ก <b>...</b> ทำตัวหนาเน้นหัวข้อ
• ใช้แท็ก <a href="...">...</a> แปลงลิงก์ยาวยืดเป็นปุ่มคลิกดูคลิปสั้นๆ
• ใช้แท็ก <code>${eventId}</code> ฟีเจอร์พิเศษของ Telegram: แตะที่ Event ID 1 ครั้งจะคัดลอกลงคลิปบอร์ดทันที!
• ทำไมต้องแยกตัวแปรกับ Telegram? เพราะ LINE ไม่รองรับแท็ก HTML หากส่ง <b> ไป LINE จะโชว์แท็กดิบๆ บนจออย่างน่าเกลียด
• LINE จึงต้องใช้ข้อความธรรมดา (Plain Text) และวางลิงก์ ${clipUrl} ตรงๆ เพื่อให้แอป LINE แปลงเป็นลิงก์กดได้อัตโนมัติ
- กฎเหล็กสูงสุดของ n8n: ข้อมูลที่จะส่งให้โหนดถัดไป (เช่น โหนด Telegram Bot หรือโหนด HTTP Request ของ LINE) ต้องครอบด้วยออบเจกต์ที่มีคีย์ชื่อว่า
jsonเสมอ หาก return ข้อความเปล่าๆ โหนดถัดไปจะอ่านไม่เจอ (No data returned) - telegram_parse_mode: 'HTML': สั่งให้โหนด Telegram Bot เรนเดอร์แท็กตัวหนาและลิงก์คลิป
- broadcast_ready: true: ตัวแปรยืนยันความสมบูรณ์เพื่อให้โหนด Switch หรือ Filter ถัดไปตรวจสอบก่อนยิงแจ้งเตือน
| ท่อนโค้ด / คำสั่งสคริปต์ | คืออะไร & หมายความว่ายังไง? | ทำทำไม? (เหตุผล & ตรรกะ) | ทำแล้วได้อะไร? (ผลลัพธ์) |
|---|---|---|---|
|
const body = $input.item.json.body || {};
🔴 กฎเหล็ก
|
ประกาศตัวแปรคงที่ const เพื่อรับข้อมูลจากโหนดก่อนหน้าผ่าน $input.item.json และใส่ || {} ป้องกันกรณีไม่มีฟิลด์ body
|
ป้องกัน Runtime Crash ข้อผิดพลาด Cannot read property of undefined หาก Webhook ส่งข้อมูลมาไม่ครบ
|
ได้ออบเจกต์ body ที่ปลอดภัย 100% ต่อให้ไม่มีข้อมูลก็ไม่ทำให้สคริปต์พังกลางคัน
|
|
const plate = body.plate || '1กข 4567';
🟡 ค่าสำรอง
|
อ่านค่าหมายเลขทะเบียนรถจาก body.plate ถ้าไม่มีค่าให้ใช้ค่าสตริงตัวอย่าง '1กข 4567'
|
ในการทดสอบรันบน n8n ผู้เรียนอาจยิง JSON ว่างเข้ามา การมี Fallback ช่วยให้เห็นหน้าตาผลลัพธ์โดยไม่ต้องกรอกข้อมูลครบทุกฟิลด์ |
ข้อความแจ้งเตือนจะมีเลขทะเบียนรถปรากฏเสมอ ไม่กลายเป็นคำว่า undefined บนหน้าจอเจ้าหน้าที่
|
|
const province = body.province || 'สุรินทร์';
|
อ่านค่าจังหวัดจาก body.province พร้อมสำรองค่าจังหวัดที่ตั้งศูนย์
|
กล้อง LPR บางมุมอาจจับภาพจังหวัดไม่ชัด การมี Fallback ช่วยรักษาฟอร์แมตข้อความให้สมบูรณ์ | ข้อความแจ้งเตือนมีชื่อจังหวัดแสดงเสมอ ป้องกันเจ้าหน้าที่สับสน |
|
const camera = body.camera_name || body.camera || body.camera_group || body.group || '...';
⚡ Auto Camera / Group Detect
|
ตรวจจับชื่อกล้องอัตโนมัติ (camera_name หรือ camera) หากไม่มีให้สลับไปดึงชื่อกลุ่มกล้อง (camera_group หรือ group) แทนแบบ Cascading Fallback | กรณีกล้องบางจุดยังไม่ได้ตั้งชื่อเฉพาะ หรือเป็นกล้องที่ดึงผ่านสตรีมแบบกลุ่ม การดึงชื่อกลุ่มกล้องแทนจะช่วยให้ศูนย์สั่งการยังรู้ว่าเกิดเหตุในโซน/สายทางใด | ได้ตำแหน่งกล้องหรือโซนกลุ่มกล้องเสมอ 100% ไม่ว่างเปล่าและไม่ขึ้นคำว่า undefined |
|
new Date().toLocaleString('th-TH', { timeZone: 'Asia/Bangkok' })
⚡ เวลาไทย
|
ฟังก์ชันแปลงเวลาปัจจุบันของระบบเป็นรูปแบบภาษาไทยและล็อกเขตเวลาเป็นเวลาประเทศไทย (UTC+7) | Server n8n มักตั้งค่าเวลาเครื่องเป็น UTC หากไม่กำหนดเขตเวลา เวลาในข้อความจะช้ากว่าเวลาจริง 7 ชั่วโมง | วันเวลาเกิดเหตุตรงกับเวลาจริงในประเทศไทย 100% ใช้เป็นหลักฐานทางคดีได้ทันที |
|
encodeURIComponent(camera)
🔴 สำคัญมาก
|
ฟังก์ชันแปลงอักขระพิเศษและภาษาไทย เช่น เว้นวรรค วงเล็บ หรือสระ ให้เป็นรหัส URL เปอร์เซ็นต์ (เช่น %20)
|
ชื่อกล้องภาษาไทยถ้าต่อเข้าไปใน URL โดยตรง ลิงก์จะขาด หรือเบราว์เซอร์จะไม่สามารถเปิดวิดีโอคลิปได้ | ได้ลิงก์เปิดคลิปวิดีโอที่คลิกแล้วทำงานได้บนสมาร์ตโฟนและคอมพิวเตอร์ทุกเครื่อง 100% |
|
<b>...</b>, <a href="...">, <code>
|
แท็กภาษา HTML ที่ Telegram Bot API รองรับสำหรับการจัดรูปแบบข้อความ |
สร้างตัวหนา แปลง URL ยาวยืดให้เป็นปุ่มคลิก และแท็ก <code> ช่วยให้แตะเพื่อคัดลอก Event ID ได้ทันที
|
ข้อความอ่านง่าย เป็นระเบียบ สะดวกต่อการใช้งานบนหน้าจอมือถือสายตรวจ |
|
const lineMessage = `...`;
|
ตัวแปรเก็บข้อความสำหรับส่งเข้า LINE โดยใช้ Plain Text ปราศจากแท็ก HTML | LINE ไม่รองรับแท็ก HTML หากนำข้อความของ Telegram มาส่งจะเห็นแท็กหลุดออกมาบนหน้าจอ | ข้อความบน LINE สวยงาม สะอาดตา และลิงก์คลิปวิดีโอคลิกได้ปกติ |
|
return { json: { ... } };
🔴 กฎเหล็ก
|
ส่งผลลัพธ์กลับสู่ Workflow โดยต้องครอบด้วยออบเจกต์ที่มีคีย์ชื่อว่า json
|
n8n ถูกออกแบบให้ประมวลผลข้อมูลผ่านคีย์ json หาก return สตริงเปล่าๆ โหนดถัดไปจะแจ้งว่า "No data returned"
|
ข้อมูลทั้งหมดถูกส่งต่อไปยังโหนด Telegram, LINE หรือ Respond to Webhook ได้อย่างราบรื่น |
{
"type": "alert",
"body": {
"plate": "2ขง 8899",
"province": "สุรินทร์",
"camera_name": "กล้องด่านตรวจหนองกวาง 01",
"camera": "cam-checkpoint-01",
"camera_group": "กลุ่มจุดตรวจหลักสาย 333",
"danger_level": "🔴 อันตรายสูงสุด (หมายจับคดียาเสพติดร้ายแรง)",
"owner_name": "นายวันชัย ดวงแก้ว",
"event_id": "ev-lpr-20260910-01"
}
}
💡 วิธีทดสอบ: คัดลอก JSON ด้านบนนี้ไปวางในคำสั่ง cURL หรือยิงเข้า Webhook URL โหนดแรกของท่านเพื่อทดสอบการฟอร์แมต
คู่มือการติดตั้งและตั้งค่า Telegram Bot เมื่อนำระบบ BPP-VMS ไปลงเครื่องใหม่
ขั้นตอนการสร้างบอตกับ @BotFather, การหา Chat ID (แชทเดี่ยว & กลุ่มสายตรวจ), การกรอกค่าใน .env และ n8n Credential พร้อมคำสั่งทดสอบ 1-Click Verification
เปิดแอป Telegram บนมือถือหรือ PC แล้วค้นหาผู้ใช้ชื่อ @BotFather (ต้องมีเครื่องหมายติ๊กถูกสีฟ้า Verified เท่านั้น ป้องกันบอตปลอม)
BPP-VMS Alert Botbpp215_surin_alert_bot (ห้ามซ้ำกับผู้อื่น)7182938475:AAFl9xK... (เก็บเป็นความลับ ห้ามเผยแพร่)- พิมพ์คำสั่ง
/setprivacy➔ เลือกบอต ➔ กดเลือก Disable (เพื่อให้บอตอ่านข้อความคำสั่งและรูปภาพในกลุ่มสายตรวจได้) - พิมพ์คำสั่ง
/setjoingroups➔ เลือกบอต ➔ กดเลือก Enable (อนุญาตให้ดึงเข้ากลุ่มได้)
1. ทักบอตที่เราเพิ่งสร้าง แล้วกดปุ่ม Start (หรือพิมพ์ /start) 1 ครั้ง เพื่อเปิดสิทธิ์ให้บอตส่งข้อความหาเราได้
2. ไปที่บอต @userinfobot หรือ @RawDataBot แล้วกด Start จะได้ตัวเลข Id: 123456789 (เป็นเลขบวก)
- ดึงบอตที่เราสร้างเข้ากลุ่มสายตรวจ
- ตั้งค่าบอตให้เป็น "Admin" ของกลุ่ม: เปิดสิทธิ์ Send Messages, Send Photos, Embed Links
- ดึงบอต
@RawDataBotเข้ากลุ่มชั่วคราว แล้วมองหาบรรทัด"chat": { "id": -100xxxxxxxxxx } - เมื่อได้เลขแล้ว ให้เตะ @RawDataBot ออกจากกลุ่มได้ทันที
Chat ID ของกลุ่ม Telegram ต้องมีเครื่องหมายลบ - หรือ -100 นำหน้าเสมอ! เช่น -1002148921456
หากเผลอตัดเครื่องหมายลบออก บอตจะส่งไม่ไปและเกิดข้อผิดพลาด 400 Bad Request: chat not found ทันที!
เปิดไฟล์ frigate-nvr/.env บนเครื่องเซิร์ฟเวอร์ใหม่ แล้วกรอกค่าทั้ง 3 บรรทัด:
# 1. แจ้งเตือนป้ายทะเบียน LPR และ AI ตรวจจับวัตถุประจำกล้อง
LPR_TELEGRAM_BOT_TOKEN=7182938475:AAFl9xKxxxxxxxxxxxxxxxxxxxxx
LPR_TELEGRAM_CHAT_ID=-1002148921456
# 2. บอตสืบสวนแฟ้มคดี NAS และสกัดพิกัด GPS ภาพถ่าย (nas-bot-service)
TELEGRAM_BOT_TOKEN=7182938475:AAFl9xKxxxxxxxxxxxxxxxxxxxxx
docker compose up -d lpr-service nas-bot-service
- เข้าหน้าเว็บ n8n:
http://<IP_เครื่องใหม่>:5678 - ไปที่เมนู Credentials (ไอคอนรูปกุญแจ) ➔ คลิก Add Credential
- พิมพ์ค้นหา Telegram API ➔ วางค่า Access Token ที่ได้จาก @BotFather ➔ คลิก Save
- ใน Workflow ของ Chapter 3 ให้เลือก Credential นี้ แล้วใส่
Chat IDที่ช่องปลายทาง
ก่อนเปิดใช้งานระบบจริง ให้ทดสอบยิงข้อความผ่านคำสั่ง Terminal บนเครื่องใหม่ เพื่อยืนยันว่า Token และ Chat ID ใช้งานได้ 100%:
curl -s -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" -d "chat_id=<CHAT_ID>" -d "text=🚨 [ทดสอบระบบ BPP-VMS] บอตเชื่อมต่อเครื่องใหม่สำเร็จเรียบร้อยแล้วครับ! 🟢" -d "parse_mode=HTML"
docker exec -it lpr-service python -c "from app import send_telegram_alert; send_telegram_alert('🚨 ทดสอบส่งแจ้งเตือนจากเครื่องใหม่สำเร็จ 100%', None)"
| รหัสข้อผิดพลาด / อาการ | สาเหตุที่แท้จริง | วิธีแก้ไขหน้างาน | วิธีป้องกันในระยะยาว |
|---|---|---|---|
| 401 Unauthorized | Token ไม่ถูกต้อง, มีช่องว่างเกิน หรือคัดลอกมาไม่ครบทุกตัวอักษร |
กลับไปที่ @BotFather พิมพ์ /token แล้วคัดลอก Token ทั้งชุดมาวางใหม่ใน .env
|
ห้ามแก้ไข Token ด้วยมือ ให้ใช้การคลิกคัดลอกโดยตรงจาก Telegram |
| 400 Bad Request: chat not found |
1. แชทส่วนตัว: ผู้รับยังไม่เคยกด /start กับบอต2. แชทกลุ่ม: ลืมใส่เครื่องหมายลบ -100
|
- แชทส่วนตัว: ทักบอตแล้วกดปุ่ม Start 1 ครั้ง - แชทกลุ่ม: ตรวจสอบ Chat ID ให้ขึ้นต้นด้วย -100 เช่น -1002148921456
|
ดึงบอต @RawDataBot เข้ากลุ่มเพื่อตรวจสอบ ID ที่มีเครื่องหมายลบติดมาให้แน่นอน |
| 400 Bad Request: can't parse entities |
ข้อความมีแท็ก HTML ผิดรูปแบบ เช่น มี <b> แต่ไม่มี </b> ปิด
|
ตรวจเช็กใน Code Node ให้ปิดแท็ก HTML ให้ครบ หรือส่งข้อความสำรองแบบ Plain Text |
ใช้ Helper Function ปิดแท็กอัตโนมัติ หรือใช้ Fallback plain_text ในสคริปต์
|
| 403 Forbidden: not enough rights to send photos | บอตในกลุ่มสายตรวจไม่ได้สิทธิ์ส่งภาพ หรือกลุ่มล็อกสิทธิ์ไม่ให้สมาชิกส่งมีเดีย | เข้าไปที่ Group Settings ➔ Administrators ➔ ตั้งบอตเป็น Admin และเปิดสิทธิ์ Post Photos & Links | ตั้งบอตเป็น Admin เสมอเมื่อใช้งานในกลุ่มภารกิจความมั่นคง |