อินเทอร์เฟซ Third-Party

สร้างเครื่องมือที่ BakoshApp สามารถควบคุมได้: ให้บริการ HTTP สี่เอนด์พอยต์บนพอร์ตในเครื่อง แล้วโปรแกรมของคุณจะตอบสนองต่อของขวัญ ไลก์ การติดตาม และแชทจาก TikTok และ Kick

BakoshApp สามารถรันแอ็กชันภายในโปรแกรมของคุณได้ เครื่องมือของคุณให้บริการ HTTP API ขนาดเล็กบนพอร์ตในเครื่อง BakoshApp จะอ่านรายการแอ็กชันของคุณและเรียกใช้หนึ่งรายการทุกครั้งที่เหตุการณ์ TikTok หรือ Kick ตรงกับทริกเกอร์ที่ผู้ใช้ตั้งไว้ อะไรก็ตามที่คุณควบคุมได้ด้วยโค้ด — ไฟอัจฉริยะ ไมโครคอนโทรลเลอร์ ซาวด์บอร์ด เกมอื่น หรือบอทของคุณเอง — สามารถตอบสนองต่อไลฟ์สตรีมได้โดยไม่ต้องมีม็อด BakoshApp

การทำงานร่วมกัน

  1. เครื่องมือของคุณรับฟังที่ http://127.0.0.1:8833
  2. ผู้ใช้เชื่อมต่อเครื่องมือนี้ใน การตั้งค่า → Third-Party (โฮสต์และพอร์ต) แล้วตรวจสอบด้วย ทดสอบการเชื่อมต่อ
  3. ในแท็บ การกระทำ ของม็อด ผู้ใช้คลิก เพิ่มใหม่ เลือกทริกเกอร์ แล้วตั้งค่า Third-Party Action ใน การกระทำทั้งหมด → ภายนอก: BakoshApp จะเติมสองรายการด้วยหมวดหมู่และแอ็กชันของคุณ
  4. ทุกครั้งที่มีเหตุการณ์ที่ตรงกัน 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 อธิบายเหตุการณ์ที่ทำให้การกระทำทำงาน จะมีเฉพาะฟิลด์ที่มีอยู่จริงสำหรับเหตุการณ์นั้นเท่านั้น — ตรวจสอบก่อนอ่านค่า

ฟิลด์ประเภทมีอยู่
triggerTypeIdnumberเสมอ — ดูด้านล่าง
eventTypestringเสมอ — gift, like, chat, follow, share, joined, subscribe, tikfinity หรือ test
sourcestringเสมอ — eulerstream (TikTok), kick, tikfinity หรือ test
platformstringเหตุการณ์ TikTok และ Kick — tiktok หรือ kick
usernamestringเสมอ — แฮนเดิลของผู้ชม
nicknamestringเหตุการณ์ TikTok และ Kick — ชื่อที่แสดง
userIdstringเหตุการณ์ TikTok และ Kick
profilePictureUrlstringส่วนใหญ่ (ลิงก์อวาตาร์ของ TikTok จะหมดอายุ)
commentstringข้อความแชท; บน Kick ยังรวมถึงข้อความที่แนบมากับของขวัญด้วย
giftNamestringของขวัญ
giftIdnumberของขวัญ TikTok
repeatCountnumberของขวัญ TikTok — จำนวนของขวัญในสตรีค; การสมัครสมาชิกแบบให้เป็นของขวัญของ Kick — จำนวนผู้รับ
diamondCountnumberของขวัญ — มูลค่าเป็น Diamonds (TikTok) หรือราคาเป็น Kicks (Kick)
coinsnumberเหมือนกับ diamondCount
likeCountnumberไลก์
subMonthnumberการสมัครสมาชิก

เหตุการณ์ที่ส่งต่อมาจาก TikFinity จะมีเพียง triggerTypeId (เป็น 0 เสมอ), source, eventType และ username เท่านั้น

ประเภททริกเกอร์

triggerTypeId คือประเภทของทริกเกอร์ที่ผู้ใช้ตั้งค่าไว้ เพื่อแยกความแตกต่างระหว่างของขวัญที่เลือกตามชื่อกับของขวัญที่จับคู่ตามมูลค่า:

IDทริกเกอร์
1แชร์
3Min-Max (ช่วงมูลค่าของขวัญ)
4ของขวัญ
6เข้าร่วม
7ไลก์
9ติดตาม
10Subscribe / Super Fan
11แชท
0TikFinity หรืออื่นๆ

ถือว่า 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}`));

การทดสอบเครื่องมือของคุณ

  1. เริ่มเครื่องมือของคุณ
  2. ใน BakoshApp, เปิด การตั้งค่า → Third-Party ตรวจสอบโฮสต์และพอร์ต แล้วคลิก ทดสอบการเชื่อมต่อ คุณควรเห็นชื่อ เวอร์ชัน และผู้พัฒนาของเครื่องมือของคุณ
  3. ในแท็บ การกระทำ ของม็อด คลิก เพิ่มใหม่ เลือกทริกเกอร์ แล้วคลิก ถัดไป ใน การกระทำทั้งหมด → ภายนอก คลิก Configure ข้าง Third-Party Action เลือกหมวดหมู่และแอ็กชันของคุณ แล้วบันทึก
  4. คลิก ทดสอบ ที่แถวใหม่ — เครื่องมือของคุณจะได้รับ exec ที่มี eventType: "test"
  5. ไลฟ์จริงแล้วตรวจสอบว่าเหตุการณ์จริงส่งมาถึง

ถ้าตัวกำหนดค่าแสดงข้อความ "เชื่อมต่อไม่ได้! ตรวจสอบการเชื่อมต่อของคุณ" ให้ตรวจสอบว่าเครื่องมือรับฟังที่ 127.0.0.1 พอร์ตตรงกับใน การตั้งค่า และ GET /api/app/info ส่งคืนคำตอบไว้ภายใน data