Интерфейс 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 показывает его как «Подключено к Mock Tool 1.0 от 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" }
  ]
}

Списки показывают элементы в том порядке, в котором вы их возвращаете. Действие, которое пользователь уже сохранил, но которое вы больше не возвращаете, остаётся вверху списка, чтобы оно не подменилось незаметно.

Предупреждение

Для настроенного действия 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 ждёт ответа до 5 секунд от трёх GET-эндпоинтов и до 10 секунд от exec. Запрос, который завершился ошибкой или по таймауту, не повторяется.
  • Если у действия Кол-во больше 1, BakoshApp вызывает exec столько раз подряд: дожидается вашего ответа, затем выжидает Интервал строки и вызывает снова.
  • Вызовы для одной строки идут по одному; разные строки могут вызывать вас параллельно.

Поэтому отвечайте быстро, а медленную работу выполняйте в фоне — зрители могут присылать подарки быстрее, чем успевает последовательный порт.

Объект 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 заставляет BakoshApp считать ответ ошибкой — даже при HTTP 200.
  • В ответе на exec поле message записывается в консоль и лог-файл BakoshApp как предупреждение. Всплывающего окна пользователь не увидит.

HTTP-статусы ошибок тоже учитываются; без message в логе будет только код статуса.

Совместимость с TikFinity

Интерфейс совпадает со сторонним интерфейсом TikFinity — те же четыре пути и названия полей, — поэтому один инструмент может работать с обоими. Отличаются порты: TikFinity ищет инструменты на порту 8832, который занимает запущенный BakoshApp, поэтому для BakoshApp укажите другой порт.

TikFinity — веб-приложение, ему нужны CORS-заголовки (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.