Интерфейс 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 показывает его как «Подключено к 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 | Шер |
| 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
Интерфейс совпадает со сторонним интерфейсом 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}`));Проверка инструмента
- Запустите свой инструмент.
- В BakoshApp откройте Настройки → Third-Party, проверьте хост и порт и нажмите Проверить подключение. Должны появиться название, версия и автор вашего инструмента.
- На вкладке мода Действия нажмите Добавить новый, выберите триггер и нажмите Далее. В разделе Все действия → Внешние нажмите Configure рядом с Third-Party Action, выберите свою категорию и действие и сохраните.
- Нажмите Тест в новой строке — инструмент получит
execсeventType: "test". - Выйдите в эфир и убедитесь, что приходят реальные события.
Если конфигуратор пишет «Не удалось подключиться! Проверьте интеграцию.», убедитесь, что инструмент слушает 127.0.0.1, что порт совпадает с указанным в разделе Настройки и что GET /api/app/info возвращает ответ внутри data.