Open AlexXanderGrib opened 3 years ago
Не скажу что "официальный" подход, просто поделюсь личными предпочтениями и немного практической части.
Я предпочитаю использовать подход monorepo для организации распределённых модулей (собственно его использует библиотека). Можно взять уже готовый шаблон для сервисов, и отделить реализацию бота от библиотеки с помощью абстракций (так как любое критическое изменения потребует большого внимания для его адаптирования). Так же по-хорошему стоит использовать виртуальные машины для идентичных условий в разработке и продакшене, здесь поможет например Docker.
Зависимости бота должны быть явными, т.е. никаких добавлений "фич" с помощью одного импорта, иначе тут начнётся сущий кошмар отладки. Абстрактный код:
// commands/random.ts
import { Command } from '@my-project/core';
import { getRandomIntegerInRange } from '@my-project/utils';
export const randomCommand = new Command({
slug: 'random',
aliases: [
'рандом',
'random'
],
description = 'рандмоное число в промежутке';
arguments: [
{
type: 'integer',
key: 'min',
label: 'минк/макс',
default: null
},
{
type: 'integer',
key: 'max',
label: 'минк/макс',
default: null
}
],
handler(context) {
// Работаем с аргументами, а не текстом
let { min = null, max = null } = context.commander.params;
if (min === null && max === null) {
min = 0;
max = 100;
} else if (max === null) {
max = min;
min = 0;
}
const result = getRandomIntegerInRange(min, max);
return context.answer({
text: `число в промежутке ${min} - ${max}: ${result}`
});
}
});
// commands/index.ts
export * from './random';
// bot.ts
import {
Bot,
SessionManager,
RedisSessionStorage,
RateLimitManager,
CommanderManager
} from '@my-project/core';
import * as commands from './commands';
const sessionManager = new SessionManager({
storage: new RedisSessionStorage({})
});
const rateLimitManager = new RateLimitManager({
maxPerSecond: 1
});
const commanderManager = new CommanderManager();
for (const command of Object.values(commands)) {
commanderManager.add(command);
}
const bot = new Bot({
// ...options
});
// Это может быть кастомная цепочка middleware в боте
bot.incoming.on('message', sessionManager.middleware);
bot.incoming.on('message', rateLimitManager.middleware);
bot.incoming.on('message', commanderManager.middleware);
bot.start()
.then(() => {
console.log('Bot started', error);
})
.catch((error: Error) => {
console.error('Error starting bot', error);
process.exit(1);
});
Важные вещи из кода выше:
@my-project/core
, в котором находятся вещи необходимые для бота.Dispatcher
. Зачем же это нужно? Всё очень просто — можно вызвать команду из любого места с указанными параметрами. Из текста мы парсим любым удобным способом аргументы которые описаны в команде, а кнопки в клавиатуре просто задаются уже с ними и адресуются к нужной нам команде. Тем самым мы избежали дублирование логики и организовали валидацию аргументов. Например вызов одной команды из другой:
export const dndCommand = new Command({
// ...
handler(context) {
return context.commander.enter('random', {
params: {
min: 1,
max: 20
}
});
}
});
Это подход я использовал в моих ботах, и он оказался вполне удобным для реализации от простых до сложных ботов. В лучшем случае пакет @my-project/core
должен быть только алиасом библиотеки которая уже всё реализовала и протестировала, а файл выглядит следующим образом:
export { Bot, Command } from 'super-bot-library';
export { ViewerManager } from './middlewares';
В случаях изменений в библиотеке можно тогда будет заменить один из интерфейсов. Но никто не запрещает держать всё логику только для проекта.
Есть ещё интересный вариант реализации логики бота на хуках, который применяется допустим в React
или Vue
, потыкать вживую можно используя этот код.
Спасибо, очень хороший и развёрнутый ответ. И я бы хотел попросить, тебя оставить issue открытым, чтобы другие люди тоже смогли прочитать
Привет, смотрел твой разбор ботов на ютубе, и у них у всех была одна большая проблема: весь код - портянка на 5к строк. Поэтому у меня созрел вопрос: как ты видишь, что должен быть устроен проект с VK-IO. Потому что в нынешнем виде это либо портянка, либо индекс + конфиг + куча файлов, который выглядит примерно вот так:
И у него тоже есть проблемы:
Хотелось бы услышать твой ответ на это и дублирование его в очень явном виде в документацию, и возможно в
README.md