Vk-io: АрхитСктура: Какой `ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½ΠΎ` ΠΎΠΏΡ‚ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ способ ΠΎΡ€Π³Π°Π½ΠΈΠ·Π°Ρ†ΠΈΠΈ Π±ΠΎΡ‚Π° Π½Π° VK-IO

Π‘ΠΎΠ·Π΄Π°Π½Π½Ρ‹ΠΉ Π½Π° 14 Π΄Π΅ΠΊ. 2020  Β·  2ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ  Β·  Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: negezor/vk-io

ΠŸΡ€ΠΈΠ²Π΅Ρ‚, смотрСл Ρ‚Π²ΠΎΠΉ Ρ€Π°Π·Π±ΠΎΡ€ Π±ΠΎΡ‚ΠΎΠ² Π½Π° ΡŽΡ‚ΡƒΠ±Π΅, ΠΈ Ρƒ Π½ΠΈΡ… Ρƒ всСх Π±Ρ‹Π»Π° ΠΎΠ΄Π½Π° большая ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΠ°: вСсь ΠΊΠΎΠ΄ - портянка Π½Π° 5ΠΊ строк. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ Ρƒ мСня созрСл вопрос: ΠΊΠ°ΠΊ Ρ‚Ρ‹ видишь, Ρ‡Ρ‚ΠΎ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ устроСн ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ с VK-IO. ΠŸΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ Π² Π½Ρ‹Π½Π΅ΡˆΠ½Π΅ΠΌ Π²ΠΈΠ΄Π΅ это Π»ΠΈΠ±ΠΎ портянка, Π»ΠΈΠ±ΠΎ индСкс + ΠΊΠΎΠ½Ρ„ΠΈΠ³ + ΠΊΡƒΡ‡Π° Ρ„Π°ΠΉΠ»ΠΎΠ², ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ выглядит ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ Π²ΠΎΡ‚ Ρ‚Π°ΠΊ:

Frame 4

/** <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!"))

И Ρƒ Π½Π΅Π³ΠΎ Ρ‚ΠΎΠΆΠ΅ Π΅ΡΡ‚ΡŒ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹:

  1. ΠŸΠΎΡ€ΡΠ΄ΠΎΠΊ ΠΏΠΎΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ Ρ„ΠΈΡ‡ Π² индСксС влияСт Π½Π° ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ Π±ΠΎΡ‚Π°
  2. МоТно Π·Π°Π±Ρ‹Ρ‚ΡŒ ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ Ρ„ΠΈΡ‡Ρƒ
  3. Π§Ρ‚ΠΎΠ±Ρ‹ Π±Ρ‹Π»ΠΎ ΡƒΠ΄ΠΎΠ±Π½ΠΎ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ, ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Π½Ρ‹ΠΉ экзСмпляр Π’Πš Π΄ΠΎΠ»ΠΆΠ΅Π½ Π»Π΅ΠΆΠ°Ρ‚ΡŒ Π² ΠΊΠΎΠ½Ρ„ΠΈΠ³Π΅
  4. Π‘Π»ΠΎΠΆΠ½ΠΎ ΡƒΡΠ»Π΅Π΄ΠΈΡ‚ΡŒ Π·Π° связями ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ„ΠΈΡ‡Π°ΠΌΠΈ

Π₯ΠΎΡ‚Π΅Π»ΠΎΡΡŒ Π±Ρ‹ ΡƒΡΠ»Ρ‹ΡˆΠ°Ρ‚ΡŒ Ρ‚Π²ΠΎΠΉ ΠΎΡ‚Π²Π΅Ρ‚ Π½Π° это ΠΈ Π΄ΡƒΠ±Π»ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π΅Π³ΠΎ Π² ΠΎΡ‡Π΅Π½ΡŒ явном Π²ΠΈΠ΄Π΅ Π² Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΡŽ, ΠΈ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ Π² README.md

Π‘Π°ΠΌΡ‹ΠΉ ΠΏΠΎΠ»Π΅Π·Π½Ρ‹ΠΉ ΠΊΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ

НС скаТу Ρ‡Ρ‚ΠΎ "ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ" ΠΏΠΎΠ΄Ρ…ΠΎΠ΄, просто подСлюсь Π»ΠΈΡ‡Π½Ρ‹ΠΌΠΈ прСдпочтСниями ΠΈ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ практичСской части.

ΠžΠ±Ρ‰ΠΈΠΉ Π΄ΠΈΠ·Π°ΠΉΠ½

Π― ΠΏΡ€Π΅Π΄ΠΏΠΎΡ‡ΠΈΡ‚Π°ΡŽ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ 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);
    });

Π’Π°ΠΆΠ½Ρ‹Π΅ Π²Π΅Ρ‰ΠΈ ΠΈΠ· ΠΊΠΎΠ΄Π° Π²Ρ‹ΡˆΠ΅:

  1. Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½ Π±Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΏΠ°ΠΊΠ΅Ρ‚ @my-project/core, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ находятся Π²Π΅Ρ‰ΠΈ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΡ‹Π΅ для Π±ΠΎΡ‚Π°.
  2. ΠšΠΎΠΌΠ°Π½Π΄Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹, Ρ‚Π΅ΠΌ самым рСализуя Dispatcher. Π—Π°Ρ‡Π΅ΠΌ ΠΆΠ΅ это Π½ΡƒΠΆΠ½ΠΎ? Всё ΠΎΡ‡Π΅Π½ΡŒ просто β€” ΠΌΠΎΠΆΠ½ΠΎ Π²Ρ‹Π·Π²Π°Ρ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ ΠΈΠ· любого мСста с ΡƒΠΊΠ°Π·Π°Π½Π½Ρ‹ΠΌΠΈ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌΠΈ. Из тСкста ΠΌΡ‹ парсим Π»ΡŽΠ±Ρ‹ΠΌ ΡƒΠ΄ΠΎΠ±Π½Ρ‹ΠΌ способом Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Ρ‹ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ описаны Π² ΠΊΠΎΠΌΠ°Π½Π΄Π΅, Π° ΠΊΠ½ΠΎΠΏΠΊΠΈ Π² ΠΊΠ»Π°Π²ΠΈΠ°Ρ‚ΡƒΡ€Π΅ просто Π·Π°Π΄Π°ΡŽΡ‚ΡΡ ΡƒΠΆΠ΅ с Π½ΠΈΠΌΠΈ ΠΈ Π°Π΄Ρ€Π΅ΡΡƒΡŽΡ‚ΡΡ ΠΊ Π½ΡƒΠΆΠ½ΠΎΠΉ Π½Π°ΠΌ ΠΊΠΎΠΌΠ°Π½Π΄Π΅. Π’Π΅ΠΌ самым ΠΌΡ‹ ΠΈΠ·Π±Π΅ΠΆΠ°Π»ΠΈ Π΄ΡƒΠ±Π»ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π»ΠΎΠ³ΠΈΠΊΠΈ ΠΈ ΠΎΡ€Π³Π°Π½ΠΈΠ·ΠΎΠ²Π°Π»ΠΈ Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΡŽ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ². НапримСр Π²Ρ‹Π·ΠΎΠ² ΠΎΠ΄Π½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ ΠΈΠ· Π΄Ρ€ΡƒΠ³ΠΎΠΉ:
export const dndCommand = new Command({
    // ...
    handler(context) {
        return context.commander.enter('random', {
            params: {
                min: 1,
                max: 20
            }
        });
    }
});
  1. ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ ΠΌΠΎΠ΄ΡƒΠ»ΡŒ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€Π° ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±Ρ‹Π» использован, ΠΈΠΌΠ΅Π΅Ρ‚ свою Π·ΠΎΠ½Ρƒ отвСтствСнности ΠΈ Π΄Π°Ρ‘Ρ‚ Ρ‡Ρ‘Ρ‚ΠΊΠΎΠ΅ ΠΏΠΎΠ½ΠΈΠΌΠ°Π½ΠΈΠ΅, Ρ‡Ρ‚ΠΎ использовано ΠΈ доступно.

Π—Π°ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅

Π­Ρ‚ΠΎ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ я использовал Π² ΠΌΠΎΠΈΡ… Π±ΠΎΡ‚Π°Ρ…, ΠΈ ΠΎΠ½ оказался Π²ΠΏΠΎΠ»Π½Π΅ ΡƒΠ΄ΠΎΠ±Π½Ρ‹ΠΌ для Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ ΠΎΡ‚ простых Π΄ΠΎ слоТных Π±ΠΎΡ‚ΠΎΠ². Π’ Π»ΡƒΡ‡ΡˆΠ΅ΠΌ случаС ΠΏΠ°ΠΊΠ΅Ρ‚ @my-project/core Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ алиасом Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ которая ΡƒΠΆΠ΅ всё Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π»Π° ΠΈ протСстировала, Π° Ρ„Π°ΠΉΠ» выглядит ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ:

export { Bot, Command } from 'super-bot-library';

export { ViewerManager } from './middlewares';

Π’ случаях ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ Π² Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Ρ‚ΠΎΠ³Π΄Π° Π±ΡƒΠ΄Π΅Ρ‚ Π·Π°ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΠΎΠ΄ΠΈΠ½ ΠΈΠ· интСрфСйсов. Но Π½ΠΈΠΊΡ‚ΠΎ Π½Π΅ Π·Π°ΠΏΡ€Π΅Ρ‰Π°Π΅Ρ‚ Π΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ всё Π»ΠΎΠ³ΠΈΠΊΡƒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°.

Π•ΡΡ‚ΡŒ Π΅Ρ‰Ρ‘ интСрСсный Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ Π»ΠΎΠ³ΠΈΠΊΠΈ Π±ΠΎΡ‚Π° Π½Π° Ρ…ΡƒΠΊΠ°Ρ…, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ примСняСтся допустим Π² React ΠΈΠ»ΠΈ Vue, ΠΏΠΎΡ‚Ρ‹ΠΊΠ°Ρ‚ΡŒ Π²ΠΆΠΈΠ²ΡƒΡŽ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ этот ΠΊΠΎΠ΄.

ВсС 2 ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ

НС скаТу Ρ‡Ρ‚ΠΎ "ΠΎΡ„ΠΈΡ†ΠΈΠ°Π»ΡŒΠ½Ρ‹ΠΉ" ΠΏΠΎΠ΄Ρ…ΠΎΠ΄, просто подСлюсь Π»ΠΈΡ‡Π½Ρ‹ΠΌΠΈ прСдпочтСниями ΠΈ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ практичСской части.

ΠžΠ±Ρ‰ΠΈΠΉ Π΄ΠΈΠ·Π°ΠΉΠ½

Π― ΠΏΡ€Π΅Π΄ΠΏΠΎΡ‡ΠΈΡ‚Π°ΡŽ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ 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);
    });

Π’Π°ΠΆΠ½Ρ‹Π΅ Π²Π΅Ρ‰ΠΈ ΠΈΠ· ΠΊΠΎΠ΄Π° Π²Ρ‹ΡˆΠ΅:

  1. Π Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π½ Π±Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΏΠ°ΠΊΠ΅Ρ‚ @my-project/core, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΌ находятся Π²Π΅Ρ‰ΠΈ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΡ‹Π΅ для Π±ΠΎΡ‚Π°.
  2. ΠšΠΎΠΌΠ°Π½Π΄Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹, Ρ‚Π΅ΠΌ самым рСализуя Dispatcher. Π—Π°Ρ‡Π΅ΠΌ ΠΆΠ΅ это Π½ΡƒΠΆΠ½ΠΎ? Всё ΠΎΡ‡Π΅Π½ΡŒ просто β€” ΠΌΠΎΠΆΠ½ΠΎ Π²Ρ‹Π·Π²Π°Ρ‚ΡŒ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ ΠΈΠ· любого мСста с ΡƒΠΊΠ°Π·Π°Π½Π½Ρ‹ΠΌΠΈ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌΠΈ. Из тСкста ΠΌΡ‹ парсим Π»ΡŽΠ±Ρ‹ΠΌ ΡƒΠ΄ΠΎΠ±Π½Ρ‹ΠΌ способом Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Ρ‹ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ описаны Π² ΠΊΠΎΠΌΠ°Π½Π΄Π΅, Π° ΠΊΠ½ΠΎΠΏΠΊΠΈ Π² ΠΊΠ»Π°Π²ΠΈΠ°Ρ‚ΡƒΡ€Π΅ просто Π·Π°Π΄Π°ΡŽΡ‚ΡΡ ΡƒΠΆΠ΅ с Π½ΠΈΠΌΠΈ ΠΈ Π°Π΄Ρ€Π΅ΡΡƒΡŽΡ‚ΡΡ ΠΊ Π½ΡƒΠΆΠ½ΠΎΠΉ Π½Π°ΠΌ ΠΊΠΎΠΌΠ°Π½Π΄Π΅. Π’Π΅ΠΌ самым ΠΌΡ‹ ΠΈΠ·Π±Π΅ΠΆΠ°Π»ΠΈ Π΄ΡƒΠ±Π»ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ Π»ΠΎΠ³ΠΈΠΊΠΈ ΠΈ ΠΎΡ€Π³Π°Π½ΠΈΠ·ΠΎΠ²Π°Π»ΠΈ Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΡŽ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ². НапримСр Π²Ρ‹Π·ΠΎΠ² ΠΎΠ΄Π½ΠΎΠΉ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ ΠΈΠ· Π΄Ρ€ΡƒΠ³ΠΎΠΉ:
export const dndCommand = new Command({
    // ...
    handler(context) {
        return context.commander.enter('random', {
            params: {
                min: 1,
                max: 20
            }
        });
    }
});
  1. ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ ΠΌΠΎΠ΄ΡƒΠ»ΡŒ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€Π° ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π±Ρ‹Π» использован, ΠΈΠΌΠ΅Π΅Ρ‚ свою Π·ΠΎΠ½Ρƒ отвСтствСнности ΠΈ Π΄Π°Ρ‘Ρ‚ Ρ‡Ρ‘Ρ‚ΠΊΠΎΠ΅ ΠΏΠΎΠ½ΠΈΠΌΠ°Π½ΠΈΠ΅, Ρ‡Ρ‚ΠΎ использовано ΠΈ доступно.

Π—Π°ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅

Π­Ρ‚ΠΎ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ я использовал Π² ΠΌΠΎΠΈΡ… Π±ΠΎΡ‚Π°Ρ…, ΠΈ ΠΎΠ½ оказался Π²ΠΏΠΎΠ»Π½Π΅ ΡƒΠ΄ΠΎΠ±Π½Ρ‹ΠΌ для Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ ΠΎΡ‚ простых Π΄ΠΎ слоТных Π±ΠΎΡ‚ΠΎΠ². Π’ Π»ΡƒΡ‡ΡˆΠ΅ΠΌ случаС ΠΏΠ°ΠΊΠ΅Ρ‚ @my-project/core Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±Ρ‹Ρ‚ΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ алиасом Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ которая ΡƒΠΆΠ΅ всё Ρ€Π΅Π°Π»ΠΈΠ·ΠΎΠ²Π°Π»Π° ΠΈ протСстировала, Π° Ρ„Π°ΠΉΠ» выглядит ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠΌ ΠΎΠ±Ρ€Π°Π·ΠΎΠΌ:

export { Bot, Command } from 'super-bot-library';

export { ViewerManager } from './middlewares';

Π’ случаях ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ Π² Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ΅ ΠΌΠΎΠΆΠ½ΠΎ Ρ‚ΠΎΠ³Π΄Π° Π±ΡƒΠ΄Π΅Ρ‚ Π·Π°ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΠΎΠ΄ΠΈΠ½ ΠΈΠ· интСрфСйсов. Но Π½ΠΈΠΊΡ‚ΠΎ Π½Π΅ Π·Π°ΠΏΡ€Π΅Ρ‰Π°Π΅Ρ‚ Π΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ всё Π»ΠΎΠ³ΠΈΠΊΡƒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°.

Π•ΡΡ‚ΡŒ Π΅Ρ‰Ρ‘ интСрСсный Π²Π°Ρ€ΠΈΠ°Π½Ρ‚ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ Π»ΠΎΠ³ΠΈΠΊΠΈ Π±ΠΎΡ‚Π° Π½Π° Ρ…ΡƒΠΊΠ°Ρ…, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ примСняСтся допустим Π² React ΠΈΠ»ΠΈ Vue, ΠΏΠΎΡ‚Ρ‹ΠΊΠ°Ρ‚ΡŒ Π²ΠΆΠΈΠ²ΡƒΡŽ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ этот ΠΊΠΎΠ΄.

Бпасибо, ΠΎΡ‡Π΅Π½ΡŒ Ρ…ΠΎΡ€ΠΎΡˆΠΈΠΉ ΠΈ Ρ€Π°Π·Π²Ρ‘Ρ€Π½ΡƒΡ‚Ρ‹ΠΉ ΠΎΡ‚Π²Π΅Ρ‚. И я Π±Ρ‹ Ρ…ΠΎΡ‚Π΅Π» ΠΏΠΎΠΏΡ€ΠΎΡΠΈΡ‚ΡŒ, тСбя ΠΎΡΡ‚Π°Π²ΠΈΡ‚ΡŒ issue ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚Ρ‹ΠΌ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π΄Ρ€ΡƒΠ³ΠΈΠ΅ люди Ρ‚ΠΎΠΆΠ΅ смогли ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Ρ‚ΡŒ

Π‘Ρ‹Π»Π° Π»ΠΈ эта страница ΠΏΠΎΠ»Π΅Π·Π½ΠΎΠΉ?
0 / 5 - 0 Ρ€Π΅ΠΉΡ‚ΠΈΠ½Π³ΠΈ

Π‘ΠΌΠ΅ΠΆΠ½Ρ‹Π΅ вопросы

AndreiSoroka picture AndreiSoroka  Β·  6ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ

ProgrammingLife picture ProgrammingLife  Β·  9ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ

Zharckov picture Zharckov  Β·  13ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ

alexey2baranov picture alexey2baranov  Β·  8ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ

ogmishanya picture ogmishanya  Β·  4ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ