Olá, eu assisti sua análise de bots no YouTube, e todos eles tiveram um grande problema: todo o código é um footcloth para 5k linhas. Portanto, tenho uma pergunta: como você vê que um projeto com VK-IO deve ser organizado. Porque em sua forma atual é um footcloth ou um index + config + um monte de arquivos que se parecem com isso:
/** <strong i="8">@filename</strong>: config.ts */
export const vk = new VK({ /* ... */ })
/** <strong i="11">@filename</strong>: feat1.ts */
import { vk } from "./config"
vk.updates.on('message', async(ctx, next) => {
/* code */
})
/** <strong i="14">@filename</strong>: index.ts */
import { vk } from "./config"
import "./feat1"
vk.updates.startPolling().then(() => console.log("Bot works!"))
E também tem problemas:
Adoraria ouvir sua resposta para isso e duplicá-la muito explicitamente na documentação e possivelmente README.md
Não vou dizer que a abordagem “oficial”, vou apenas compartilhar minhas preferências pessoais e um pouco da parte prática.
Eu prefiro usar a abordagem monorepo para organizar módulos distribuídos (a biblioteca realmente a usa). Você pode pegar um modelo pronto para serviços e separar a implementação do bot da biblioteca usando abstrações (já que qualquer alteração importante exigirá muita atenção para adaptá-la). Também é uma boa ideia usar máquinas virtuais para condições idênticas em desenvolvimento e produção, por exemplo, o Docker ajudará aqui.
As dependências de bot devem ser explícitas, ou seja, sem adicionar "recursos" com uma única importação, caso contrário, um verdadeiro pesadelo de depuração começará aqui. Código abstrato:
// 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);
});
Coisas importantes do código acima:
@my-project/core
, que contém as coisas necessárias para o bot.Dispatcher
. Por que isso é necessário? Tudo é muito simples - você pode chamar o comando de qualquer lugar com os parâmetros especificados. A partir do texto, analisamos os argumentos descritos no comando de qualquer maneira conveniente, e os botões no teclado já estão definidos com eles e são endereçados ao comando que precisamos. Assim, evitamos a duplicação de lógica e organizamos a validação dos argumentos. Por exemplo, chamando um comando de outro:export const dndCommand = new Command({
// ...
handler(context) {
return context.commander.enter('random', {
params: {
min: 1,
max: 20
}
});
}
});
Essa é a abordagem que usei em meus bots e acabou sendo bastante conveniente implementar desde bots simples a bots complexos. Na melhor das hipóteses, o pacote @my-project/core
deve ser apenas um alias de uma biblioteca que já implementou e testou tudo, e o arquivo fica assim:
export { Bot, Command } from 'super-bot-library';
export { ViewerManager } from './middlewares';
Em casos de alterações na biblioteca, será então possível substituir uma das interfaces. Mas ninguém proíbe manter toda a lógica apenas para o projeto.
Existe outra implementação interessante da lógica do bot nos ganchos, que é usada, por exemplo, em React
ou Vue
, você pode poke-lo ao vivo usando este código .
Obrigado por uma resposta muito boa e detalhada. E eu gostaria de pedir que você deixe o assunto em aberto para que outras pessoas possam ler também
Comentários muito úteis
Não vou dizer que a abordagem “oficial”, vou apenas compartilhar minhas preferências pessoais e um pouco da parte prática.
Projeto geral
Eu prefiro usar a abordagem monorepo para organizar módulos distribuídos (a biblioteca realmente a usa). Você pode pegar um modelo pronto para serviços e separar a implementação do bot da biblioteca usando abstrações (já que qualquer alteração importante exigirá muita atenção para adaptá-la). Também é uma boa ideia usar máquinas virtuais para condições idênticas em desenvolvimento e produção, por exemplo, o Docker ajudará aqui.
Arquitetura
As dependências de bot devem ser explícitas, ou seja, sem adicionar "recursos" com uma única importação, caso contrário, um verdadeiro pesadelo de depuração começará aqui. Código abstrato:
Coisas importantes do código acima:
@my-project/core
, que contém as coisas necessárias para o bot.Dispatcher
. Por que isso é necessário? Tudo é muito simples - você pode chamar o comando de qualquer lugar com os parâmetros especificados. A partir do texto, analisamos os argumentos descritos no comando de qualquer maneira conveniente, e os botões no teclado já estão definidos com eles e são endereçados ao comando que precisamos. Assim, evitamos a duplicação de lógica e organizamos a validação dos argumentos. Por exemplo, chamando um comando de outro:Conclusão
Essa é a abordagem que usei em meus bots e acabou sendo bastante conveniente implementar desde bots simples a bots complexos. Na melhor das hipóteses, o pacote
@my-project/core
deve ser apenas um alias de uma biblioteca que já implementou e testou tudo, e o arquivo fica assim:Em casos de alterações na biblioteca, será então possível substituir uma das interfaces. Mas ninguém proíbe manter toda a lógica apenas para o projeto.
Existe outra implementação interessante da lógica do bot nos ganchos, que é usada, por exemplo, em
React
ouVue
, você pode poke-lo ao vivo usando este código .