Telegraf WizardScene步骤回退功能实现技术求助
Telegraf WizardScene 步骤回退问题解决
问题描述
在Telegraf的WizardScene中实现多步骤表单,每次用户提交数据后编辑机器人消息展示进度。需求是除第一步外,支持从任意步骤回退至上一步。尝试用ctx.wizard.steps[ctx.wizard.cursor - 2]触发上一步逻辑,虽然能回到上一步的提问,但用户在回退后的步骤提交新内容时,流程会直接跳转到回退前位置的下一个步骤,流程示例:
- 步骤1:机器人提示“发送文本”→ 用户输入文本(进入步骤2)
- 步骤2:机器人提示“发送其他内容”→ 用户点击“返回”(回到步骤1)
- 步骤1:机器人提示“发送文本”→ 用户输入新内容→ 直接跳转到步骤3
核心原因
直接调用步骤函数但未修改ctx.wizard.cursor的值,cursor仍保留回退前的索引。当回退后的步骤执行ctx.wizard.next()时,会基于旧的cursor递增,导致跳转到错误的步骤。
解决方案
使用Telegraf WizardScene提供的selectStep()方法,该方法会同时更新cursor并执行目标步骤的处理函数。同时修复代码中的变量错误,确保步骤逻辑统一。
修改后的完整代码
const { Scenes, Markup } = require("telegraf"); module.exports = () => { const scene = new Scenes.WizardScene( "newSceneWithStapBacks", async (ctx) => { // 第一步:用户通过内联按钮进入场景 await ctx.editMessageText(`Send text`, { ...Markup.inlineKeyboard([ [Markup.button.callback(`Cancel`, `cancel`)], // 第一步只有取消按钮 ]), parse_mode: "HTML", }); // 初始化数据存储对象 ctx.wizard.state.data = {}; // 保存初始消息的上下文,用于后续编辑 ctx.wizard.state.data.firstCtx = ctx; return ctx.wizard.next(); }, async (ctx) => { // 验证输入:非文本消息直接删除 if (!ctx?.message?.text) return ctx.deleteMessage(); const smthData = ctx.message.text; // 删除用户输入的消息 await ctx.deleteMessage(); // 编辑机器人消息,展示已提交的数据并提示下一步 await ctx.wizard.state.data.firstCtx.editMessageText( `1. Your data: ${smthData} Send something else.`, { ...Markup.inlineKeyboard([ [ Markup.button.callback(`Back`, `back`), Markup.button.callback(`Cancel`, `cancel`), ], ]), parse_mode: "HTML", } ); // 保存数据到会话 ctx.wizard.state.data.smthData = smthData; return ctx.wizard.next(); }, async (ctx) => { // 验证输入:非文本消息直接删除 if (!ctx?.message?.text) return ctx.deleteMessage(); const smthData2 = ctx.message.text; // 删除用户输入的消息 await ctx.deleteMessage(); // 编辑机器人消息,展示进度 await ctx.wizard.state.data.firstCtx.editMessageText( `1. Your data: ${ctx.wizard.state.data.smthData} 2. Your data: ${smthData2} Send something else.`, { ...Markup.inlineKeyboard([ [ Markup.button.callback(`Back`, `back`), Markup.button.callback(`Cancel`, `cancel`), ], ]), parse_mode: "HTML", } ); // 保存数据 ctx.wizard.state.data.smthData2 = smthData2; return ctx.wizard.next(); }, async (ctx) => { // 验证输入:非文本消息直接删除 if (!ctx?.message?.text) return ctx.deleteMessage(); const smthData3 = ctx.message.text; // 删除用户输入的消息 await ctx.deleteMessage(); // 编辑机器人消息,展示最终结果 await ctx.wizard.state.data.firstCtx.editMessageText( `1. Your data: ${ctx.wizard.state.data.smthData} 2. Your data: ${ctx.wizard.state.data.smthData2} 3. Your data: ${smthData3} 🎉 End.`, { parse_mode: "HTML", } ); // 保存最后一项数据 ctx.wizard.state.data.smthData3 = smthData3; return ctx.wizard.leave(); } ); // 回退功能实现 scene.action(/^(?:back)$/i, async (ctx) => { // 禁止从第一步回退 if (ctx.wizard.cursor <= 1) { return ctx.answerCbQuery("无法继续回退"); } // 计算目标步骤索引:当前cursor是当前步骤的索引,回退一步即cursor-1 const targetStepIndex = ctx.wizard.cursor - 1; // 切换到目标步骤,自动更新cursor并执行步骤函数 await ctx.wizard.selectStep(targetStepIndex); return ctx.answerCbQuery(); }); // 取消场景 scene.action(/^(?:cancel)$/i, async (ctx) => { await ctx.reply(`已退出!`); return ctx.scene.leave(); }); return scene; };
关键修改点
- 回退逻辑:使用
ctx.wizard.selectStep(targetStepIndex)替代直接调用步骤函数,确保cursor被正确更新,后续的next()能基于当前步骤正常跳转。 - 变量统一:将步骤函数中的参数统一为
ctx,修复原代码中错误使用userCtx导致的逻辑异常。 - 数据保存:在最后一步正确保存
smthData3,原代码中错误重复保存smthData2。 - 操作反馈:添加
ctx.answerCbQuery()给用户按钮点击的反馈,提升交互体验。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

