Third-Party 인터페이스

BakoshApp이 제어할 수 있는 도구를 만들어보세요: 로컬 포트에서 네 개의 HTTP 엔드포인트를 제공하면, 여러분의 프로그램이 TikTok과 Kick의 선물, 좋아요, 팔로우, 채팅에 반응합니다.

BakoshApp은 여러분의 프로그램 안에서 액션을 실행할 수 있어요. 여러분의 도구는 로컬 포트에서 작은 HTTP API를 제공하고, BakoshApp은 여러분의 액션 목록을 읽어와 사용자의 트리거와 일치하는 TikTok 또는 Kick 이벤트가 발생할 때마다 그중 하나를 호출해요. 코드로 제어할 수 있는 것이라면 무엇이든 — 스마트 조명, 마이크로컨트롤러, 사운드보드, 다른 게임, 직접 만든 봇 — BakoshApp Mod 없이도 라이브 스트림에 반응할 수 있어요.

연동 구조

  1. 여러분의 도구는 http://127.0.0.1:8833에서 대기해요.
  2. 사용자는 설정 → Third-Party에서 호스트와 포트를 연결하고 연결 테스트로 확인해요.
  3. Mod의 액션 탭에서 사용자가 새로 추가를 클릭하고 트리거를 선택한 다음, 모든 액션 → 외부에서 Third-Party Action을 설정해요: BakoshApp이 두 개의 목록을 여러분의 카테고리와 액션으로 채워요.
  4. 일치하는 이벤트가 발생할 때마다 BakoshApp이 여러분의 도구를 호출하고, 여러분의 코드가 작업을 수행해요.

연결은 한 방향이에요: BakoshApp이 여러분을 호출할 뿐, 그 반대는 없어요.

포트와 주소

  • 8833이 기본값이에요. 사용자는 설정 → Third-Party에서 변경할 수 있으니, 다른 포트를 사용한다면 어떤 포트를 입력해야 하는지 사용자에게 알려주세요.
  • 8832는 사용할 수 없어요: BakoshApp 자신이 그 포트에서 대기하고 있고(이 덕분에 TikFinity가 BakoshApp을 찾을 수 있어요), 설정에서도 이 포트를 거부해요.
  • 인증은 없어요. API가 네트워크에서 접근되지 않도록 0.0.0.0이 아니라 127.0.0.1에서 대기하세요.
  • BakoshApp은 항상 일반 HTTP를 사용해요.

엔드포인트

여러분의 도구는 네 개를 모두 제공해야 해요. 모든 응답은 data 멤버를 포함한 JSON이에요.

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은 세 개의 GET 엔드포인트는 최대 5초, exec는 최대 10초까지 기다려요. 실패하거나 시간이 초과된 요청은 다시 시도되지 않아요.
  • 액션의 횟수가 1보다 크면, BakoshApp은 exec를 그 횟수만큼 차례로 호출해요: 응답을 기다린 다음, 행의 간격만큼 기다렸다가 다시 호출해요.
  • 같은 행에 대한 호출은 한 번에 하나씩 오지만, 서로 다른 행은 동시에 호출할 수 있어요.

그러니 빠르게 응답하고 느린 작업은 백그라운드에서 처리하세요 — 시청자는 시리얼 포트가 움직이는 속도보다 더 빠르게 선물을 보낼 수 있어요.

context 객체

context는 액션을 실행한 이벤트를 설명해요. 해당 이벤트에 존재하는 필드만 포함되어 있으니, 읽기 전에 먼저 확인하세요.

필드유형포함 조건
triggerTypeIdnumber항상 — 아래 참고
eventTypestring항상 — gift, like, chat, follow, share, joined, subscribe, tikfinity 또는 test
sourcestring항상 — eulerstream(TikTok), kick, tikfinity 또는 test
platformstringTikTok 및 Kick 이벤트 — tiktok 또는 kick
usernamestring항상 — 시청자의 아이디
nicknamestringTikTok 및 Kick 이벤트 — 표시 이름
userIdstringTikTok 및 Kick 이벤트
profilePictureUrlstring대부분(TikTok 아바타 링크는 만료돼요)
commentstring채팅 메시지; Kick에서는 선물에 첨부된 메시지도 포함돼요
giftNamestring선물
giftIdnumberTikTok 선물
repeatCountnumberTikTok 선물 — 연속 선물 개수; Kick 선물 구독 — 수신자 수
diamondCountnumber선물 — Diamonds 가치(TikTok) 또는 Kicks 가격(Kick)
coinsnumberdiamondCount와 동일
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가 있으면 HTTP 200이더라도 BakoshApp이 그 응답을 오류로 처리해요.
  • exec에서는 message가 경고로 BakoshApp 콘솔과 로그 파일에 기록돼요. 사용자에게 팝업은 표시되지 않아요.

HTTP 오류 상태 코드도 반영돼요. message가 없으면 로그에는 상태 코드만 표시돼요.

TikFinity와 함께 사용하기

이 인터페이스는 TikFinity의 서드파티 인터페이스와 동일해요 — 같은 네 개의 경로와 필드 이름을 사용하므로 하나의 도구로 둘 다 지원할 수 있어요. 포트는 달라요: TikFinity는 8832 포트에서 도구를 찾는데, BakoshApp이 실행 중일 때 그 포트를 사용하므로 BakoshApp에는 다른 포트를 지정하세요.

TikFinity는 웹 앱이라서 CORS 헤더(Access-Control-Allow-Origin: * 등)와 OPTIONS 프리플라이트 요청에 대한 응답이 필요해요. BakoshApp에는 필요 없지만, 보내도 문제는 없어요. BakoshApp은 TikFinity보다 더 많은 context 필드를 보내니, 사용하지 않는 필드는 무시하세요.

완전한 예제 도구

의존성 없음 — 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. Mod의 액션 탭에서 새로 추가를 클릭하고 트리거를 선택한 다음 다음을 클릭하세요. 모든 액션 → 외부에서 Third-Party Action 옆의 Configure를 클릭하고 카테고리와 액션을 선택한 다음 저장하세요.
  4. 새 행에서 테스트를 클릭하세요 — 여러분의 도구가 eventType: "test"가 담긴 exec를 받게 돼요.
  5. 실제로 라이브를 켜고 진짜 이벤트가 도착하는지 확인하세요.

설정 화면에 "연결할 수 없습니다! 연동을 확인하세요."라고 표시된다면, 도구가 127.0.0.1에서 대기 중인지, 포트가 설정과 일치하는지, GET /api/app/info가 data 안에 응답을 반환하는지 확인하세요.