NestJS의 Telegram 봇에 대한 데이터베이스의 저장 데이터를 사용하여 상황에 맞는 재귀 명령 "첫 번째 회의"의 예를 만듭니다.
100402 단어 kaufmanbotpostgresnestjsrecursive
연결
https://github.com/EndyKaufman/kaufman-bot - 봇의 소스 코드
https://telegram.me/DevelopKaufmanBot - 전보의 현재 봇
작품 설명
이 게시물에서는 재귀 상황에 맞는 명령을 만들 것입니다.
이 명령은 새로운 사용자를 만나고 그들에 대한 정보를 데이터베이스에 저장하고 나중에 사용할 것입니다.
첫 회의 만들기
필요한 모든 테이블에 대한 마이그레이션 생성
마이그레이션/V202204101203CreateFirstMeeting.pgsql
DO $$
BEGIN
CREATE TYPE "Gender" AS ENUM (
'Male',
'Female'
);
EXCEPTION
WHEN duplicate_object THEN
NULL;
END
$$;
DO $$
BEGIN
CREATE TYPE "FirstMeetingStatus" AS ENUM (
'StartMeeting',
'AskFirstname',
'AskLastname',
'AskGender',
'EndMeeting'
);
EXCEPTION
WHEN duplicate_object THEN
NULL;
END
$$;
CREATE TABLE IF NOT EXISTS "FirstMeeting" (
id uuid DEFAULT uuid_generate_v4 () NOT NULL,
"userId" uuid NOT NULL CONSTRAINT "FK_FIRST_MEETING__USER_ID" REFERENCES "User",
"status" "FirstMeetingStatus" NOT NULL,
"firstname" varchar(100) NOT NULL,
"lastname" varchar(100) NOT NULL,
"gender" "Gender" NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "PK_FIRST_MEETING" PRIMARY KEY (id)
);
CREATE UNIQUE INDEX IF NOT EXISTS "UQ_FIRST_MEETING" ON "FirstMeeting" ("userId");
데이터베이스에서 마이그레이션 적용
npm run migrate:local
endy@endy-virtual-machine:~/Projects/current/kaufman-bot$ npm run migrate:local
> [email protected] migrate:local
> export $(xargs < ./.env.local) > /dev/null 2>&1 && export DATABASE_URL=$SERVER_POSTGRES_URL && npm run migrate
> [email protected] migrate
> npm run flyway -- migrate
> [email protected] flyway
> flyway -c .flyway.js "migrate"
Flyway Community Edition 6.3.2 by Redgate
Database: jdbc:postgresql://localhost:5432/kaufman_bot_develop (PostgreSQL 13.3)
WARNING: Flyway upgrade recommended: PostgreSQL 13.3 is newer than this version of Flyway and support has not been tested. The latest supported version of PostgreSQL is 12.
Successfully validated 5 migrations (execution time 00:00.017s)
Current version of schema "public": 202204030939
Migrating schema "public" to version 202204101203 - CreateFirstMeeting
Successfully applied 1 migration to schema "public" (execution time 00:00.043s)
기존 데이터베이스에서 prisma 스키마 업데이트
npm run prisma:pull:local
endy@endy-virtual-machine:~/Projects/current/kaufman-bot$ npm run prisma:pull:local
> [email protected] prisma:pull:local
> export $(xargs < ./.env.local) > /dev/null 2>&1 && export DATABASE_URL=$SERVER_POSTGRES_URL && npm run -- prisma db pull && npm run prisma:generate
> [email protected] prisma
> prisma "db" "pull"
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "kaufman_bot_develop", schema "public" at "localhost:5432"
Introspecting based on datasource defined in prisma/schema.prisma …
✔ Introspected 4 models and wrote them into prisma/schema.prisma in 123ms
Run prisma generate to generate Prisma Client.
> [email protected] prisma:generate
> npm run -- prisma generate
> [email protected] prisma
> prisma "generate"
Prisma schema loaded from prisma/schema.prisma
✔ Generated Prisma Client (3.11.1 | library) to ./node_modules/@prisma/client in 192ms
You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
해당 명령을 실행한 후 Prisma 스키마
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl"]
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id(map: "PK_USERS") @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
telegramId String @unique(map: "UQ_USERS__TELEGRAM_ID") @db.VarChar(64)
langCode String @default("en") @db.VarChar(64)
debugMode Boolean @default(false)
DialogflowSession DialogflowSession[]
FirstMeeting FirstMeeting?
}
model migrations {
installed_rank Int @id(map: "__migrations_pk")
version String? @db.VarChar(50)
description String @db.VarChar(200)
type String @db.VarChar(20)
script String @db.VarChar(1000)
checksum Int?
installed_by String @db.VarChar(100)
installed_on DateTime @default(now()) @db.Timestamp(6)
execution_time Int
success Boolean
@@index([success], map: "__migrations_s_idx")
@@map("__migrations")
}
model DialogflowSession {
id String @id(map: "PK_DIALOGFLOW_SESSION") @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
userId String @db.Uuid
projectId String @db.VarChar(512)
sessionId String @db.Uuid
requestsMetadata Json @default("[]")
responsesMetadata Json @default("[]")
createdAt DateTime @default(now()) @db.Timestamp(6)
updatedAt DateTime @default(now()) @db.Timestamp(6)
User User @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_DIALOGFLOW_SESSION__USER_ID")
@@unique([userId, projectId, sessionId], map: "UQ_DIALOGFLOW_SESSION")
}
model FirstMeeting {
id String @id(map: "PK_FIRST_MEETING") @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
userId String @unique(map: "UQ_FIRST_MEETING") @db.Uuid
status FirstMeetingStatus
firstname String @db.VarChar(100)
lastname String @db.VarChar(100)
gender Gender
createdAt DateTime @default(now()) @db.Timestamp(6)
updatedAt DateTime @default(now()) @db.Timestamp(6)
User User @relation(fields: [userId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_FIRST_MEETING__USER_ID")
}
enum FirstMeetingStatus {
StartMeeting
AskFirstname
AskLastname
AskGender
EndMeeting
}
enum Gender {
Male
Female
}
nx 라이브러리 생성
npm run -- nx g @nrwl/nest:lib first-meeting/server
endy@endy-virtual-machine:~/Projects/current/kaufman-bot$ npm run -- nx g @nrwl/nest:lib first-meeting/server
> [email protected] nx
> nx "g" "@nrwl/nest:lib" "first-meeting/server"
CREATE libs/first-meeting/server/README.md
CREATE libs/first-meeting/server/.babelrc
CREATE libs/first-meeting/server/src/index.ts
CREATE libs/first-meeting/server/tsconfig.json
CREATE libs/first-meeting/server/tsconfig.lib.json
UPDATE tsconfig.base.json
CREATE libs/first-meeting/server/project.json
UPDATE workspace.json
CREATE libs/first-meeting/server/.eslintrc.json
CREATE libs/first-meeting/server/jest.config.js
CREATE libs/first-meeting/server/tsconfig.spec.json
CREATE libs/first-meeting/server/src/lib/first-meeting-server.module.ts
구성 만들기
libs/first-meeting/server/src/lib/first-meeting-config/first-meeting.config.ts
export const FIRST_MEETING_CONFIG = Symbol('FIRST_MEETING_CONFIG');
export interface FirstMeetingConfig {
title: string;
name: string;
descriptions: string;
usage: string[];
spyWords: string[];
}
스토리지 서비스 생성
libs/first-meeting/server/src/lib/first-meeting-services/first-meeting.storage.ts
import { PrismaClientService } from '@kaufman-bot/core/server';
import { Injectable } from '@nestjs/common';
import { FirstMeeting } from '@prisma/client';
@Injectable()
export class FirstMeetingStorage {
private readonly firstMeetingOfUsers: Record<number, FirstMeeting> = {};
constructor(private readonly prismaClientService: PrismaClientService) {}
async getUserFirstMeeting({
telegramUserId,
}: {
telegramUserId: number;
}): Promise<FirstMeeting> {
const currentFirstMeetingOfUsers: FirstMeeting =
this.firstMeetingOfUsers[this.getKey({ telegramUserId })];
if (currentFirstMeetingOfUsers) {
return currentFirstMeetingOfUsers;
}
let databaseFirstMeetingOfUsers: FirstMeeting | null = null;
try {
databaseFirstMeetingOfUsers =
await this.prismaClientService.firstMeeting.findFirst({
where: {
User: { telegramId: telegramUserId.toString() },
},
rejectOnNotFound: true,
});
} catch (error) {
databaseFirstMeetingOfUsers =
await this.prismaClientService.firstMeeting.create({
data: {
firstname: '',
lastname: '',
gender: 'Male',
status: 'StartMeeting',
User: {
connectOrCreate: {
create: { telegramId: telegramUserId.toString() },
where: { telegramId: telegramUserId.toString() },
},
},
},
});
}
this.firstMeetingOfUsers[this.getKey({ telegramUserId })] =
databaseFirstMeetingOfUsers;
return this.firstMeetingOfUsers[this.getKey({ telegramUserId })];
}
async removeUserFirstMeeting({ telegramUserId }: { telegramUserId: number }) {
delete this.firstMeetingOfUsers[this.getKey({ telegramUserId })];
await this.prismaClientService.firstMeeting.deleteMany({
where: {
User: { telegramId: telegramUserId.toString() },
},
});
}
async pathUserFirstMeeting({
telegramUserId,
firstMeeting,
}: {
telegramUserId: number;
firstMeeting: Partial<FirstMeeting>;
}) {
const currentUserFirstMeeting = this.getUserFirstMeeting({
telegramUserId,
});
await this.prismaClientService.firstMeeting.updateMany({
data: {
...currentUserFirstMeeting,
...firstMeeting,
updatedAt: new Date(),
},
where: {
User: { telegramId: telegramUserId.toString() },
},
});
delete this.firstMeetingOfUsers[this.getKey({ telegramUserId })];
this.firstMeetingOfUsers[this.getKey({ telegramUserId })] =
await this.getUserFirstMeeting({ telegramUserId });
}
private getKey({ telegramUserId }: { telegramUserId: number }) {
return telegramUserId.toString();
}
}
서비스 만들기
libs/first-meeting/server/src/lib/first-meeting-services/first-meeting.service.ts
import {
BotCommandsEnum,
BotCommandsProvider,
BotCommandsProviderActionMsg,
BotCommandsProviderActionResultType,
BotСommandsToolsService,
OnAfterBotCommands,
OnBeforeBotCommands,
OnContextBotCommands,
} from '@kaufman-bot/core/server';
import { DEFAULT_LANGUAGE } from '@kaufman-bot/language-swither/server';
import { Inject, Injectable } from '@nestjs/common';
import { FirstMeeting } from '@prisma/client';
import { getText } from 'class-validator-multi-lang';
import { TranslatesService, TranslatesStorage } from 'nestjs-translates';
import {
FirstMeetingConfig,
FIRST_MEETING_CONFIG,
} from '../first-meeting-config/first-meeting.config';
import { FirstMeetingStorage } from './first-meeting.storage';
@Injectable()
export class FirstMeetingService
implements
BotCommandsProvider,
OnAfterBotCommands,
OnContextBotCommands,
OnBeforeBotCommands
{
constructor(
@Inject(FIRST_MEETING_CONFIG)
private readonly firstMeetingConfig: FirstMeetingConfig,
private readonly botСommandsToolsService: BotСommandsToolsService,
private readonly translatesStorage: TranslatesStorage,
private readonly translatesService: TranslatesService,
private readonly firstMeetingStorage: FirstMeetingStorage
) {}
async onBeforeBotCommands<
TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
>(msg: TMsg): Promise<TMsg> {
if (msg.botStart) {
msg.text = 'meet start';
msg.botStart = false;
}
return msg;
}
async onContextBotCommands<
TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
>(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
let locale = msg.from?.language_code;
if (
!locale ||
!Object.keys(this.translatesStorage.translates).find((key) =>
locale?.includes(key)
)
) {
locale = DEFAULT_LANGUAGE;
}
const contextFirstMeeting: Partial<FirstMeeting> =
msg.botCommandHandlerContext;
if (
this.botСommandsToolsService.checkCommands(
msg.text,
[this.firstMeetingConfig.name],
locale
) ||
Object.keys(contextFirstMeeting).length > 0
) {
if (
this.botСommandsToolsService.checkCommands(
msg.text,
[
getText('exit'),
getText('reset'),
getText('cancel'),
getText('stop'),
getText('end'),
],
locale
)
) {
await this.firstMeetingStorage.pathUserFirstMeeting({
telegramUserId: msg.from.id,
firstMeeting: {
...msg.botCommandHandlerContext,
status: 'EndMeeting',
},
});
return {
type: 'text',
text: this.translatesService.translate(
getText(`{{close}} Meeting canceled`),
locale,
{ close: '❌' }
),
message: msg,
context: { status: 'EndMeeting' },
};
}
if (contextFirstMeeting?.status === 'AskFirstname') {
return {
type: 'text',
text: this.translatesService.translate(
getText(`What is your last name?`),
locale
),
message: msg,
context: <Partial<FirstMeeting>>{
...msg.botCommandHandlerContext,
status: 'AskLastname',
firstname: this.prepareText(msg.text, locale) || 'Unknown',
},
};
}
if (contextFirstMeeting?.status === 'AskLastname') {
return {
type: 'text',
text: this.translatesService.translate(
getText(`What is your gender?`),
locale
),
message: msg,
context: <Partial<FirstMeeting>>{
...msg.botCommandHandlerContext,
status: 'AskGender',
lastname: this.prepareText(msg.text, locale),
},
};
}
if (contextFirstMeeting?.status === 'AskGender') {
const firstMeeting: Partial<FirstMeeting> = {
...contextFirstMeeting,
status: 'EndMeeting',
gender: this.botСommandsToolsService.checkCommands(
this.prepareText(msg.text, locale),
[getText('female'), getText('fm')],
locale
)
? 'Female'
: 'Male',
};
await this.firstMeetingStorage.pathUserFirstMeeting({
telegramUserId: msg.from.id,
firstMeeting,
});
return {
type: 'text',
text: this.translatesService.translate(
this.getRandomItem([
getText(
`Nice to meet you, {{meetGender}} {{firstname}} {{lastname}} {{vulcan}}`
),
getText(`Nice to meet you, {{firstname}} {{vulcan}}`),
]),
locale,
{
vulcan: '🖖',
...firstMeeting,
meetGender: this.mapGenderToMeetGender(firstMeeting, locale),
firstname: this.capitalizeFirstLetter(
firstMeeting.firstname,
locale
),
lastname: this.capitalizeFirstLetter(
firstMeeting.lastname,
locale
),
}
),
message: msg,
context: <Partial<FirstMeeting>>{ status: 'EndMeeting' },
};
}
}
return null;
}
async onAfterBotCommands<
TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
>(
result: BotCommandsProviderActionResultType<TMsg>,
msg: TMsg
): Promise<{ result: BotCommandsProviderActionResultType<TMsg>; msg: TMsg }> {
if (msg.botStart) {
await this.firstMeetingStorage.removeUserFirstMeeting({
telegramUserId: msg.from.id,
});
}
if (result === null) {
msg.text = `${this.firstMeetingConfig.name} ${msg.text}`;
const newResult = await this.onMessage<TMsg>(msg);
if (newResult !== null) {
return { result: newResult, msg };
}
}
return { result, msg };
}
async onHelp<
TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
>(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
return await this.onMessage({
...msg,
text: `${this.firstMeetingConfig.name} ${BotCommandsEnum.help}`,
});
}
async onMessage<
TMsg extends BotCommandsProviderActionMsg = BotCommandsProviderActionMsg
>(msg: TMsg): Promise<BotCommandsProviderActionResultType<TMsg>> {
let locale = msg.from?.language_code;
if (
!locale ||
!Object.keys(this.translatesStorage.translates).find((key) =>
locale?.includes(key)
)
) {
locale = DEFAULT_LANGUAGE;
}
const firstMeeting = await this.firstMeetingStorage.getUserFirstMeeting({
telegramUserId: msg.from.id,
});
const spyWord = this.firstMeetingConfig.spyWords.find((spyWord) =>
this.botСommandsToolsService.checkCommands(msg.text, [spyWord], locale)
);
if (spyWord) {
if (
this.botСommandsToolsService.checkCommands(
msg.text,
[BotCommandsEnum.help],
locale
)
) {
return {
type: 'markdown',
message: msg,
markdown: this.botСommandsToolsService.generateHelpMessage({
locale,
name: this.firstMeetingConfig.title,
descriptions: this.firstMeetingConfig.descriptions,
usage: this.firstMeetingConfig.usage,
}),
};
}
if (
this.botСommandsToolsService.checkCommands(
msg.text,
[BotCommandsEnum.reset],
locale
)
) {
await this.firstMeetingStorage.removeUserFirstMeeting({
telegramUserId: msg.from.id,
});
return {
type: 'text',
text: this.translatesService.translate(
this.getRandomItem([
getText('Your meeting information has been deleted {{unamused}}'),
getText('I forgot about your existence {{worried}}'),
]),
locale,
{
unamused: '😒',
worried: '😟',
}
),
message: msg,
};
}
}
if (
!this.botСommandsToolsService.checkCommands(
msg.text,
[BotCommandsEnum.help],
locale
) &&
((spyWord &&
this.botСommandsToolsService.checkCommands(
msg.text,
[getText('start')],
locale
)) ||
firstMeeting?.status === 'StartMeeting')
) {
await this.firstMeetingStorage.pathUserFirstMeeting({
telegramUserId: msg.from.id,
firstMeeting: {
status: 'AskFirstname',
firstname: '',
lastname: '',
gender: 'Male',
},
});
return {
type: 'text',
text: this.translatesService.translate(
this.getRandomItem([
getText(`Hey! I'm Endy {{smile}}, what's your name?`),
getText(`Hey! what's your name?`),
]),
locale,
{ smile: '🙂' }
),
message: msg,
context: <Partial<FirstMeeting>>{ status: 'AskFirstname' },
};
}
if (
firstMeeting.status === 'EndMeeting' &&
this.botСommandsToolsService.checkCommands(
msg.text,
[getText('hi'), getText('hello'), getText('hey')],
locale
)
) {
return {
type: 'markdown',
markdown: this.translatesService
.translate(
this.getRandomItem([
getText(
`Hello {{meetGender}} {{firstname}} {{lastname}} {{vulcan}}`
),
getText(`Hello {{firstname}} {{lastname}} {{handsplayed}}`),
getText(`I'm glad to see you {{firstname}} {{wink}}`),
getText(`Hi {{firstname}} {{vulcan}}`),
]),
locale,
{
vulcan: '🖖',
handsplayed: '🖐',
wink: '😉',
...firstMeeting,
meetGender: this.mapGenderToMeetGender(firstMeeting, locale),
firstname: this.capitalizeFirstLetter(
firstMeeting.firstname,
locale
),
lastname: this.capitalizeFirstLetter(
firstMeeting.lastname,
locale
),
}
)
.split(' ')
.join(' ')
.split(' ')
.join(' '),
message: msg,
context: {},
};
}
return null;
}
private mapGenderToMeetGender(
firstMeeting: Partial<FirstMeeting>,
locale: string
) {
return this.translatesService.translate(
firstMeeting.gender === 'Female' ? getText('Madam') : getText('Sir'),
locale
);
}
private prepareText(text: string, locale: string) {
if (
this.botСommandsToolsService.checkCommands(
text,
[getText('skip'), getText('next')],
locale
)
) {
return '';
}
return this.botСommandsToolsService
.clearCommands(
text,
[
getText('I'),
getText('hi'),
getText('hello'),
getText('hey'),
getText('am'),
getText('my'),
getText('is'),
getText('name'),
getText('lastname'),
getText('firstname'),
getText('last'),
getText('first'),
],
locale
)
.trim();
}
private getRandomItem(items: string[]) {
return items[Math.floor(Math.random() * items.length)];
}
private capitalizeFirstLetter(text: string | undefined, locale: string) {
const [first, ...rest] = (text || '').trim();
return (first || '').toLocaleUpperCase(locale) + rest.join('');
}
}
모듈 생성
libs/first-meeting/server/src/lib/first-meeting.module.ts
import {
BotCommandsModule,
BOT_COMMANDS_PROVIDER,
PrismaClientModule,
} from '@kaufman-bot/core/server';
import { DynamicModule, Module } from '@nestjs/common';
import { getText } from 'class-validator-multi-lang';
import { TranslatesModule } from 'nestjs-translates';
import {
FirstMeetingConfig,
FIRST_MEETING_CONFIG,
} from './first-meeting-config/first-meeting.config';
import { FirstMeetingService } from './first-meeting-services/first-meeting.service';
import { FirstMeetingStorage } from './first-meeting-services/first-meeting.storage';
@Module({
imports: [TranslatesModule, BotCommandsModule],
exports: [TranslatesModule, BotCommandsModule],
})
export class FirstMeetingModule {
static forRoot(): DynamicModule {
return {
module: FirstMeetingModule,
imports: [PrismaClientModule],
providers: [
FirstMeetingStorage,
{
provide: FIRST_MEETING_CONFIG,
useValue: <FirstMeetingConfig>{
title: getText('First meeting'),
name: 'meet',
descriptions: getText(
'Example of recursive contextable commands "first meeting"'
),
usage: [
getText('meet start'),
getText('meet reset'),
getText('meet help'),
],
spyWords: [getText('meet')],
},
},
{
provide: BOT_COMMANDS_PROVIDER,
useClass: FirstMeetingService,
},
],
exports: [PrismaClientModule],
};
}
}
AppModule에 FirstMeetingModule 추가
앱/서버/src/app/app.module.ts
...
@Module({
imports: [
...
ShortCommandsModule.forRoot({
commands: {
en: {
joke: `get jokes`,
'quote|thought|wisdom': 'get quotes',
'facts|fact|history': 'get facts',
'forgot me': 'meet reset',
'what you can do|faq': 'help',
},
ru: {
'joke|jokes|шутка|дай шутку|шутки|пошути|шути|рассмеши|смешинки|смешинка':
'get jokes',
'quote|thought|wisdom|цитата|дай цитату|цитаты|цитируй|мысль|мудрость|залечи':
'get quotes',
'facts|fact|history|дай факт|факты|история': 'get facts',
'forgot me|забудь меня': 'meet reset',
'what you can do|faq|что ты умеешь|справка': 'help',
},
},
}),
CurrencyConverterModule.forRoot(),
FactsGeneratorModule.forRoot(),
QuotesGeneratorModule.forRoot(),
JokesGeneratorModule.forRoot(),
FirstMeetingModule.forRoot(),
DialogflowModule.forRoot({
projectId: env.get('DIALOGFLOW_PROJECT_ID').required().asString(),
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
AppService 업데이트
앱/서버/src/app/app.service.ts
import { BotСommandsService } from '@kaufman-bot/core/server';
import { Injectable, Logger } from '@nestjs/common';
import { On, Start, Update } from 'nestjs-telegraf';
import { Context } from 'telegraf';
@Update()
@Injectable()
export class AppService {
private readonly logger = new Logger(AppService.name);
constructor(private readonly botСommandsService: BotСommandsService) {}
getData(): { message: string } {
return { message: 'Welcome to server!' };
}
@Start()
async startCommand(ctx: Context) {
await this.botСommandsService.start(ctx);
}
@On('sticker')
async onSticker(ctx) {
await this.botСommandsService.process(ctx);
}
@On('text')
async onMessage(ctx) {
await this.botСommandsService.process(ctx);
}
}
po 사전을 json으로 변환하기 위한 파일 준비
npm run generate
포 편집기로 모든 단어 번역
텔레그램 봇의 새로운 로직 확인
도움말 메시지
먼저 봇에 쓰기
봇에 두 번째 쓰기
사용자 정보 제거
러시아어로
다음 게시물에서는 그룹에서 지원 작업을 추가하고 이를 위해 글로벌 봇 이름을 사용합니다...
Reference
이 문제에 관하여(NestJS의 Telegram 봇에 대한 데이터베이스의 저장 데이터를 사용하여 상황에 맞는 재귀 명령 "첫 번째 회의"의 예를 만듭니다.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/endykaufman/create-example-of-recursive-contextable-commands-first-meeting-with-store-data-in-database-for-telegram-bot-on-nestjs-4n9k텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)