Nuevo artículo: Diálogos en bots de Telegram con Node.js
Fecha de publicación: 2024-10-22He publicado un nuevo artículo en Medium y Habr — esta vez sobre la creación de diálogos interactivos en bots de Telegram usando la plataforma Node.js con el framework grammY.
En el artículo explico en detalle cómo procesar la entrada del usuario, gestionar el estado del diálogo, implementar bifurcaciones, bucles y evitar efectos secundarios en la lógica del bot.
Puedes ver cómo funciona en el bot de demostración: @f64_demo_conversation_bot
El artículo también incluye un repositorio con el código fuente completo: @flancer64/tg-demo-all/conversation
Fragmentos adicionales de código fuente
import {session} from ‘grammy’;
import {conversations} from ‘@grammyjs/conversations’; bot.use(session({initial: () => ({})}));
bot.use(conversations());
const conv = async (conversation, ctx) => {
} * conversation: an object that manages the state of the current conversation. * ctx: the standard grammY context corresponding to the current interaction between the user and the bot (the message).
import {createConversation} from ‘@grammyjs/conversations’; bot.use(createConversation(conv, ‘conversationStart’));
const cmd = async (ctx) => {
await ctx.conversation.enter(‘conversationStart’);
}
const conv = async (conversation, ctx) => {
return;
}; If for some reason the conversation cannot end properly (for example, the user enters another command instead of following the conversation script), you can forcibly terminate the conversation through ctx.conversation.exit(). For example:
bot.use(async (ctx, next) => {
if (ctx?.chat && (typeof ctx?.conversation?.active === ‘function’)) {
const {start} = await ctx.conversation.active();
if (start >= 1) {
logger.info(An active conversation exists.);
const commandEntity = ctx.message?.entities?.find(entity => entity.type === ‘bot_command’);
if (commandEntity) {
await ctx.conversation.exit(‘conversationStart’);
await ctx.reply(The previous conversation has been closed.);
}
}
}
await next();
});
const conv = async (conversation, ctx) => {
const username = ctx.from.username;
const sess = conversation.session;
sess.count = sess.count ?? 0;
sess.count++; logger.info(username: ${username}, count: ${sess.count});
};
await ctx.reply(Please select a service by number:\n${list});
let selected;
const response = await conversation.wait(); const id = parseInt(response.message.text);
if (!selected) await ctx.reply(Invalid selection. Please enter a valid service number.);
} while (!selected);
10/21 17:34:53.294 (info Demo_Back_Mod_Service): Service ‘Service 3’ read successfully (id:3).
const user = await conversation.external(
const dto = modUser.composeEntity();
dto.telegramId = telegramId;
modUser.create({dto});
}
); In this case, the user creation will only be executed once, during the very first call to the external method. On subsequent steps of the dialogue, the result of the first execution will be returned, and the external service won’t be called again.
let service = await conversation.external({
});
let selected;
const response = await conversation.wait(); const id = parseInt(response.message.text);
}); if (!selected) await ctx.reply(Invalid selection. Please enter a valid service number.); } while (!selected); As you can see, the external service (Demo_Back_Mod_Service) is no longer called repeatedly for incorrect values (4 and 5), while the Demo_Back_Mod_User service is invoked every time (since it is not wrapped inexternal):
10/21 17:47:01.764 (info Demo_Back_Mod_Service): Service ‘Service 3’ read successfully (id:3).
const confirmation = await conversation.wait();
const confirmationText = confirmation.message.text.toLowerCase();
if (confirmationText === ‘yes’) {
} else if (confirmationText === ‘no’) {
} else {
}
let confirmed = false;
while (!confirmed) {
const confirmation = await conversation.wait();
const confirmationText = confirmation.message.text.toLowerCase();
if (confirmationText === ‘yes’) {
} else if (confirmationText === ‘no’) {
} else {
await ctx.reply(Please respond with "yes" or "no".);
}
}