อินเทอร์เฟซ Third-Party
สร้างเครื่องมือที่ BakoshApp สามารถควบคุมได้: ให้บริการ HTTP สี่เอนด์พอยต์บนพอร์ตในเครื่อง แล้วโปรแกรมของคุณจะตอบสนองต่อของขวัญ ไลก์ การติดตาม และแชทจาก TikTok และ Kick
BakoshApp สามารถรันแอ็กชันภายในโปรแกรมของคุณได้ เครื่องมือของคุณให้บริการ HTTP API ขนาดเล็กบนพอร์ตในเครื่อง BakoshApp จะอ่านรายการแอ็กชันของคุณและเรียกใช้หนึ่งรายการทุกครั้งที่เหตุการณ์ TikTok หรือ Kick ตรงกับทริกเกอร์ที่ผู้ใช้ตั้งไว้ อะไรก็ตามที่คุณควบคุมได้ด้วยโค้ด — ไฟอัจฉริยะ ไมโครคอนโทรลเลอร์ ซาวด์บอร์ด เกมอื่น หรือบอทของคุณเอง — สามารถตอบสนองต่อไลฟ์สตรีมได้โดยไม่ต้องมีม็อด BakoshApp
การทำงานร่วมกัน
- เครื่องมือของคุณรับฟังที่
http://127.0.0.1:8833 - ผู้ใช้เชื่อมต่อเครื่องมือนี้ใน การตั้งค่า → Third-Party (โฮสต์และพอร์ต) แล้วตรวจสอบด้วย ทดสอบการเชื่อมต่อ
- ในแท็บ การกระทำ ของม็อด ผู้ใช้คลิก เพิ่มใหม่ เลือกทริกเกอร์ แล้วตั้งค่า Third-Party Action ใน การกระทำทั้งหมด → ภายนอก: BakoshApp จะเติมสองรายการด้วยหมวดหมู่และแอ็กชันของคุณ
- ทุกครั้งที่มีเหตุการณ์ที่ตรงกัน BakoshApp จะเรียกเครื่องมือของคุณ แล้วโค้ดของคุณจะทำงานที่เหลือ
การเชื่อมต่อเป็นแบบทางเดียว: BakoshApp เป็นฝ่ายเรียกคุณ ไม่ใช่ในทางกลับกัน
พอร์ตและที่อยู่
- 8833 เป็นค่าเริ่มต้น ผู้ใช้สามารถเปลี่ยนได้ใน การตั้งค่า → Third-Party หากคุณใช้พอร์ตอื่น ให้แจ้งผู้ใช้ของคุณว่าต้องกรอกพอร์ตใด
- ใช้พอร์ต 8832 ไม่ได้: BakoshApp เองรับฟังอยู่ที่พอร์ตนั้น (นั่นคือวิธีที่ TikFinity ค้นหา BakoshApp) และการตั้งค่าจะปฏิเสธพอร์ตนี้
- ไม่มีการยืนยันตัวตน ให้รับฟังที่
127.0.0.1ไม่ใช่0.0.0.0เพื่อไม่ให้ API เข้าถึงได้จากเครือข่ายภายนอก - BakoshApp ใช้ HTTP ธรรมดาเสมอ
เอนด์พอยต์
เครื่องมือของคุณต้องให้บริการครบทั้งสี่เอนด์พอยต์ ทุกการตอบกลับเป็น JSON ที่มีสมาชิก data
1. ข้อมูลแอป
ระบุตัวตนของเครื่องมือของคุณ BakoshApp จะแสดงข้อความว่า "Connected with Mock Tool 1.0 by Bakosh" ในหน้าการตั้งค่าและในตัวกำหนดค่าแอ็กชัน
GET /api/app/info
{ "data": { "name": "Mock Tool", "version": "1.0", "author": "Bakosh" } }2. หมวดหมู่
รายการแรกในตัวกำหนดค่า
GET /api/features/categories
{
"data": [
{ "categoryId": "smart_device_control", "categoryName": "Control Smart Devices" },
{ "categoryId": "serial_port_control", "categoryName": "Control Serial Port" }
]
}3. การกระทำ
รายการที่สอง: การกระทำของหมวดหมู่หนึ่ง categoryId ถูกเข้ารหัส URL
GET /api/features/actions?categoryId=smart_device_control
{
"data": [
{ "actionId": "turn_lights_on", "actionName": "Turn Lights On" },
{ "actionId": "turn_lights_off", "actionName": "Turn Lights Off" }
]
}รายการจะแสดงไอเทมของคุณตามลำดับที่คุณส่งกลับมา การกระทำที่ผู้ใช้บันทึกไว้แล้วแต่คุณไม่ได้ส่งกลับมาอีกจะยังคงอยู่ด้านบนสุดของรายการ เพื่อไม่ให้เปลี่ยนแปลงไปโดยที่ผู้ใช้ไม่รู้ตัว
categoryId และ actionId คือทั้งหมดที่ BakoshApp เก็บไว้สำหรับการกระทำที่ตั้งค่าไว้ ต้องรักษาให้คงที่: การเปลี่ยนชื่อ id จะทำให้การตั้งค่าที่มีอยู่เดิมใช้งานไม่ได้ ส่วนชื่อมีไว้แสดงผลเท่านั้นและเปลี่ยนแปลงเมื่อใดก็ได้
4. เรียกใช้งาน
ถูกเรียกทุกครั้งที่มีเหตุการณ์ที่ตรงกันสั่งให้การกระทำทำงาน
POST /api/features/actions/exec
{
"categoryId": "smart_device_control",
"actionId": "turn_lights_on",
"context": {
"triggerTypeId": 4,
"eventType": "gift",
"source": "eulerstream",
"platform": "tiktok",
"userId": "123456789",
"username": "testuser",
"nickname": "Test User",
"giftName": "Rose",
"giftId": 5655,
"repeatCount": 3,
"diamondCount": 1,
"coins": 1,
"profilePictureUrl": "https://…"
}
}ตอบกลับด้วยอาร์เรย์ data ว่างเปล่า เมื่อคุณยอมรับคำขอแล้ว:
{ "data": [] }เวลา
- BakoshApp จะรอสูงสุด 5 วินาที สำหรับสามเอนด์พอยต์ GET และ 10 วินาที สำหรับ
execคำขอที่ล้มเหลวหรือหมดเวลาจะไม่ถูกส่งซ้ำ - ถ้าการกระทำมี จำนวน มากกว่า 1, BakoshApp จะเรียก
execตามจำนวนนั้นทีละครั้ง: รอคำตอบของคุณก่อน จากนั้นรอตาม ช่วงเวลา ของแถว แล้วจึงเรียกอีกครั้ง - การเรียกสำหรับแถวเดียวกันจะมาทีละครั้ง ส่วนแถวที่ต่างกันสามารถเรียกคุณพร้อมกันได้
ดังนั้นควรตอบกลับให้เร็ว แล้วค่อยทำงานที่ช้าในพื้นหลัง — ผู้ชมสามารถส่งของขวัญได้เร็วกว่าที่พอร์ตซีเรียลจะขยับทัน
ออบเจกต์ context
context อธิบายเหตุการณ์ที่ทำให้การกระทำทำงาน จะมีเฉพาะฟิลด์ที่มีอยู่จริงสำหรับเหตุการณ์นั้นเท่านั้น — ตรวจสอบก่อนอ่านค่า
| ฟิลด์ | ประเภท | มีอยู่ |
|---|---|---|
triggerTypeId | number | เสมอ — ดูด้านล่าง |
eventType | string | เสมอ — gift, like, chat, follow, share, joined, subscribe, tikfinity หรือ test |
source | string | เสมอ — eulerstream (TikTok), kick, tikfinity หรือ test |
platform | string | เหตุการณ์ TikTok และ Kick — tiktok หรือ kick |
username | string | เสมอ — แฮนเดิลของผู้ชม |
nickname | string | เหตุการณ์ TikTok และ Kick — ชื่อที่แสดง |
userId | string | เหตุการณ์ TikTok และ Kick |
profilePictureUrl | string | ส่วนใหญ่ (ลิงก์อวาตาร์ของ TikTok จะหมดอายุ) |
comment | string | ข้อความแชท; บน Kick ยังรวมถึงข้อความที่แนบมากับของขวัญด้วย |
giftName | string | ของขวัญ |
giftId | number | ของขวัญ TikTok |
repeatCount | number | ของขวัญ TikTok — จำนวนของขวัญในสตรีค; การสมัครสมาชิกแบบให้เป็นของขวัญของ Kick — จำนวนผู้รับ |
diamondCount | number | ของขวัญ — มูลค่าเป็น Diamonds (TikTok) หรือราคาเป็น Kicks (Kick) |
coins | number | เหมือนกับ diamondCount |
likeCount | number | ไลก์ |
subMonth | number | การสมัครสมาชิก |
เหตุการณ์ที่ส่งต่อมาจาก TikFinity จะมีเพียง triggerTypeId (เป็น 0 เสมอ), source, eventType และ username เท่านั้น
ประเภททริกเกอร์
triggerTypeId คือประเภทของทริกเกอร์ที่ผู้ใช้ตั้งค่าไว้ เพื่อแยกความแตกต่างระหว่างของขวัญที่เลือกตามชื่อกับของขวัญที่จับคู่ตามมูลค่า:
| ID | ทริกเกอร์ |
|---|---|
| 1 | แชร์ |
| 3 | Min-Max (ช่วงมูลค่าของขวัญ) |
| 4 | ของขวัญ |
| 6 | เข้าร่วม |
| 7 | ไลก์ |
| 9 | ติดตาม |
| 10 | Subscribe / Super Fan |
| 11 | แชท |
| 0 | TikFinity หรืออื่นๆ |
ถือว่า id ที่ไม่รู้จักเป็น 0; อาจมีการเพิ่มประเภทใหม่ในอนาคต
ปุ่มทดสอบ
ทดสอบ ในแถวของการกระทำ (และปุ่มลัดของมัน) จะเรียก exec ด้วยข้อมูลตัวอย่าง: source และ eventType เป็น test, triggerTypeId มาจากทริกเกอร์ของแถวนั้น ผู้ชมคือ bakosh_app และของขวัญคือ Rose (giftId 5655) โดยไม่มี platform
การรายงานปัญหา
ส่งคืน message เพื่ออธิบายว่าเกิดข้อผิดพลาดอะไร:
{ "message": "Serial port COM3 is not available" }- บนเอนด์พอยต์ GET การมี
messageใดๆ จะทำให้ BakoshApp ถือว่าคำตอบนั้นเป็นข้อผิดพลาด แม้จะได้ HTTP 200 ก็ตาม - บน
exec,messageจะถูกเขียนลงในคอนโซลและไฟล์ล็อกของ BakoshApp เป็นคำเตือน ผู้ใช้จะไม่เห็นป๊อปอัป
สถานะข้อผิดพลาดของ HTTP ก็มีผลเช่นกัน; หากไม่มี message ล็อกจะแสดงเพียงรหัสสถานะเท่านั้น
ใช้งานร่วมกับ TikFinity ได้ด้วย
อินเทอร์เฟซนี้ตรงกับ third-party interface ของ TikFinity — เส้นทางและชื่อฟิลด์เดียวกันทั้งสี่รายการ — ดังนั้นเครื่องมือเดียวสามารถให้บริการทั้งสองได้ พอร์ตต่างกัน: TikFinity จะมองหาเครื่องมือที่พอร์ต 8832 ซึ่ง BakoshApp ครองอยู่ขณะทำงาน ดังนั้นให้ตั้งพอร์ตอื่นสำหรับ BakoshApp
TikFinity เป็นเว็บแอปและต้องการ CORS header (Access-Control-Allow-Origin: * และอื่นๆ) รวมถึงต้องตอบคำขอ preflight แบบ OPTIONS BakoshApp ไม่ต้องการสิ่งเหล่านี้ แต่การส่งไปก็ไม่เป็นอันตราย BakoshApp จะส่งฟิลด์ context มากกว่า TikFinity — ฟิลด์ที่คุณไม่ได้ใช้ก็เพิกเฉยได้
เครื่องมือที่ใช้งานได้จริงแบบสมบูรณ์
ไม่ต้องพึ่งพาไลบรารีใดๆ — บันทึกเป็น tool.mjs แล้วรัน node tool.mjs:
import http from 'node:http';
const PORT = 8833;
const APP_INFO = { name: 'Mock Tool', version: '1.0', author: 'Bakosh' };
const CATALOG = [
{
categoryId: 'smart_device_control',
categoryName: 'Control Smart Devices',
actions: [
{ actionId: 'turn_lights_on', actionName: 'Turn Lights On' },
{ actionId: 'turn_lights_off', actionName: 'Turn Lights Off' },
],
},
];
// Wildcard CORS keeps the same tool usable from TikFinity as well.
const CORS = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Methods': '*',
};
const send = (res, status, body) => {
res.writeHead(status, { 'Content-Type': 'application/json', ...CORS });
res.end(JSON.stringify(body));
};
const readBody = (req) =>
new Promise((resolve) => {
let raw = '';
req.on('data', (c) => { raw += c; });
req.on('end', () => { try { resolve(JSON.parse(raw || '{}')); } catch { resolve({}); } });
});
// Your actual work goes here. Return a string to report a problem.
function runAction(categoryId, actionId, context) {
console.log(`${categoryId}/${actionId} fired by ${context.username ?? 'someone'} on ${context.platform ?? context.source}`);
// turnLightsOn() …
return null;
}
http.createServer(async (req, res) => {
const url = new URL(req.url, `http://127.0.0.1:${PORT}`);
if (req.method === 'OPTIONS') { // preflight (TikFinity only)
res.writeHead(204, CORS);
return res.end();
}
if (req.method === 'GET' && url.pathname === '/api/app/info') {
return send(res, 200, { data: APP_INFO });
}
if (req.method === 'GET' && url.pathname === '/api/features/categories') {
return send(res, 200, {
data: CATALOG.map(({ categoryId, categoryName }) => ({ categoryId, categoryName })),
});
}
if (req.method === 'GET' && url.pathname === '/api/features/actions') {
const cat = CATALOG.find((c) => c.categoryId === url.searchParams.get('categoryId'));
return send(res, 200, { data: cat ? cat.actions : [] });
}
if (req.method === 'POST' && url.pathname === '/api/features/actions/exec') {
const body = await readBody(req);
const problem = runAction(body.categoryId ?? '', body.actionId ?? '', body.context ?? {});
return problem ? send(res, 200, { message: problem }) : send(res, 200, { data: [] });
}
send(res, 404, { message: `No such endpoint: ${url.pathname}` });
}).listen(PORT, '127.0.0.1', () => console.log(`Listening on http://127.0.0.1:${PORT}`));การทดสอบเครื่องมือของคุณ
- เริ่มเครื่องมือของคุณ
- ใน BakoshApp, เปิด การตั้งค่า → Third-Party ตรวจสอบโฮสต์และพอร์ต แล้วคลิก ทดสอบการเชื่อมต่อ คุณควรเห็นชื่อ เวอร์ชัน และผู้พัฒนาของเครื่องมือของคุณ
- ในแท็บ การกระทำ ของม็อด คลิก เพิ่มใหม่ เลือกทริกเกอร์ แล้วคลิก ถัดไป ใน การกระทำทั้งหมด → ภายนอก คลิก Configure ข้าง Third-Party Action เลือกหมวดหมู่และแอ็กชันของคุณ แล้วบันทึก
- คลิก ทดสอบ ที่แถวใหม่ — เครื่องมือของคุณจะได้รับ
execที่มีeventType: "test" - ไลฟ์จริงแล้วตรวจสอบว่าเหตุการณ์จริงส่งมาถึง
ถ้าตัวกำหนดค่าแสดงข้อความ "เชื่อมต่อไม่ได้! ตรวจสอบการเชื่อมต่อของคุณ" ให้ตรวจสอบว่าเครื่องมือรับฟังที่ 127.0.0.1 พอร์ตตรงกับใน การตั้งค่า และ GET /api/app/info ส่งคืนคำตอบไว้ภายใน data