Third-Party インターフェース

ローカルポートで 4 つの HTTP エンドポイントを提供して BakoshApp から操作できるツールを作り、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 は、あなたのカテゴリとアクションで 2 つのリストを埋めます。
  4. 一致するイベントが起きるたびに BakoshApp がツールを呼び出し、あなたのコードが処理を行います。

接続は一方向です。BakoshApp があなたのツールを呼び出し、その逆はありません。

ポートとアドレス

  • デフォルトは 8833 です。ユーザーは設定 → Third-Party で変更できます。別のポートを使う場合は、どのポートを入力すればよいかをユーザーに伝えてください。
  • 8832 は使えません。BakoshApp 自身がこのポートで待ち受けており(TikFinity はこれで BakoshApp を見つけます)、設定でも受け付けられません。
  • 認証はありません。API にネットワークからアクセスされないよう、0.0.0.0 ではなく 127.0.0.1 で待ち受けてください。
  • BakoshApp は常にプレーンな HTTP を使います。

エンドポイント

ツールは 4 つすべてを提供する必要があります。レスポンスはすべて、data メンバーを持つ JSON です。

1. アプリ情報

ツールを識別します。BakoshApp は、設定画面とアクションの設定画面に「Bakosh の Mock Tool 1.0 に接続しました」と表示します。

GET /api/app/info
{ "data": { "name": "Mock Tool", "version": "1.0", "author": "Bakosh" } }

2. カテゴリ

設定画面の 1 つ目のリストです。

GET /api/features/categories
{
  "data": [
    { "categoryId": "smart_device_control", "categoryName": "Control Smart Devices" },
    { "categoryId": "serial_port_control",  "categoryName": "Control Serial Port" }
  ]
}

3. アクション

2 つ目のリストで、1 つのカテゴリに属するアクションです。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" }
  ]
}

リストには、ツールが返した順に項目が表示されます。ユーザーが保存済みのアクションをツールが返さなくなっても、そのアクションはリストの先頭に残るため、知らないうちに変わることはありません。

警告

設定済みのアクションについて BakoshApp が保存するのは、categoryId と actionId だけです。これらは固定しておいてください。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 は、3 つの GET エンドポイントでは最大 5 秒、exec では最大 10 秒待ちます。失敗またはタイムアウトしたリクエストは再送されません。
  • アクションの回数が 1 より大きい場合、BakoshApp はその回数だけ exec を順番に呼び出します。応答を待ち、次にその行の間隔だけ待ってから、再び呼び出します。
  • 同じ行からの呼び出しは 1 つずつ届きますが、異なる行からは並行して呼び出されることがあります。

そのため、応答はすばやく返し、時間のかかる処理はバックグラウンドで行ってください。視聴者は、シリアルポートが動くよりも速くギフトを送れます。

context オブジェクト

context は、アクションを実行したイベントを表します。そのイベントに存在するフィールドだけが含まれるため、読み取る前に有無を確認してください。

フィールド種類含まれる条件
triggerTypeId数値常に — 下記を参照
eventType文字列常に — gift、like、chat、follow、share、joined、subscribe、tikfinity、test のいずれか
source文字列常に — eulerstream(TikTok)、kick、tikfinity、test のいずれか
platform文字列TikTok と Kick のイベント — tiktok または kick
username文字列常に — 視聴者のハンドル名
nickname文字列TikTok と Kick のイベント — 表示名
userId文字列TikTok と Kick のイベント
profilePictureUrl文字列通常は含まれる(TikTok のアバターのリンクには有効期限があります)
comment文字列チャットメッセージ。Kick ではギフトに添えられたメッセージも含む
giftName文字列ギフト
giftId数値TikTok のギフト
repeatCount数値TikTok のギフト — ストリーク内のギフト数。Kick のギフトサブスク — 受け取った人数
diamondCount数値ギフト — ダイヤ換算の価値(TikTok)または Kicks での価格(Kick)
coins数値diamondCount と同じ
likeCount数値いいね
subMonth数値サブスクリプション

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 のサードパーティインターフェースと同じ(4 つのパスとフィールド名が共通)なので、1 つのツールで両方に対応できます。ただしポートは異なります。TikFinity はポート 8832 でツールを探しますが、このポートは実行中の BakoshApp が使用しているため、BakoshApp には別のポートを割り当ててください。

TikFinity は Web アプリなので、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 の中に入れて返しているかを確認してください。