基于GAS开发的Telegram机器人:如何捕获私聊回复实现对话式参数交互?
核心结论
完全不用切换到Polling模式,Webhook下就能实现你要的对话式交互!关键在于解决会话状态持久化和非命令消息的处理这两个问题,下面给你一步步拆解方案:
1. 解决GAS易失性:用持久化存储会话状态
GAS的内存变量确实会在脚本执行结束后清空,但我们可以用Google提供的持久化存储方案来保存用户的会话状态,推荐两种方式:
- Properties Service:轻量且易用,适合存储简单的会话状态(比如当前需要收集的参数类型)。可以用
PropertiesService.getUserProperties()为每个用户单独存储状态,键可以设为chatId_state(比如123456_state),值设为类似waiting_for_read_range、waiting_for_write_data这样的标记。 - Google Sheet专用表:如果需要存储更复杂的状态(比如临时保存已输入的部分参数),可以在你的交互Sheet里新增一个“会话状态”工作表,每行记录
chatId、currentState、timestamp、tempData等字段。
2. 捕获用户回复:在doPost里处理所有消息
Telegram的Webhook会把用户发送的所有消息(包括纯文本回复、命令)都推送到你的doPost函数,所以你不需要依赖斜杠命令来触发脚本——只要在doPost里解析收到的update对象,结合用户的会话状态,就能判断该做什么:
具体逻辑流程:
- 解析
update,提取chatId、用户发送的文本text、userId等信息。 - 从持久化存储中读取该用户的当前会话状态。
- 如果状态为空:判断用户发的是
/read还是/write,设置对应的会话状态,然后发送第一个参数的询问。 - 如果状态不为空:根据状态标记,把用户的纯文本回复当作对应参数处理,完成后更新状态(比如切换到下一个参数的收集状态,或者完成操作后清空状态)。
3. 关于隐私模式的疑问
在私聊场景下,不管机器人的隐私模式设置如何,机器人都能收到用户发送的所有消息——这是Telegram的固有特性,所以你完全不需要修改隐私模式,私聊回复会正常触发doPost。
4. 为什么不用Polling模式?
Polling模式需要定时触发GAS脚本去拉取Telegram的getUpdates接口,但GAS的脚本执行时间限制是6分钟,超时会被强制终止,反而容易出现交互中断的问题。而Webhook是事件驱动,只有用户发消息时才会触发脚本,效率更高,也更稳定。
简单代码示例
下面是一个简化版的实现,用Properties Service存储状态:
function doPost(e) { const update = JSON.parse(e.postData.contents); const chatId = update.message.chat.id; const text = update.message.text.trim(); // 获取用户专属的属性存储 const userProps = PropertiesService.getUserProperties(); const currentState = userProps.getProperty(`state_${chatId}`); if (!currentState) { // 初始状态:处理命令 if (text === '/read') { userProps.setProperty(`state_${chatId}`, 'waiting_for_read_range'); sendTgMessage(chatId, '请输入要读取的表格范围(比如A1:B5):'); } else if (text === '/write') { userProps.setProperty(`state_${chatId}`, 'waiting_for_write_range'); sendTgMessage(chatId, '请输入要写入的表格范围(比如A1:C1):'); } else { sendTgMessage(chatId, '请发送 /read 或 /write 开始操作哦~'); } } else { // 会话中:根据状态处理用户回复 switch(currentState) { case 'waiting_for_read_range': try { const sheet = SpreadsheetApp.openById('你的SheetID').getActiveSheet(); const data = sheet.getRange(text).getValues(); sendTgMessage(chatId, `读取到的数据:\n${JSON.stringify(data).replace(/,/g, '\n')}`); } catch(err) { sendTgMessage(chatId, '范围格式不对,请重新输入(比如A1:B5):'); // 保持当前状态,让用户重新输入 return; } // 清空状态 userProps.deleteProperty(`state_${chatId}`); break; case 'waiting_for_write_range': // 临时保存范围,切换到收集数据的状态 userProps.setProperty(`temp_write_range_${chatId}`, text); userProps.setProperty(`state_${chatId}`, 'waiting_for_write_data'); sendTgMessage(chatId, '请输入要写入的数据(用逗号分隔,比如"张三,25,北京"):'); break; case 'waiting_for_write_data': try { const writeRange = userProps.getProperty(`temp_write_range_${chatId}`); const dataArray = text.split(',').map(item => item.trim()); const sheet = SpreadsheetApp.openById('你的SheetID').getActiveSheet(); sheet.getRange(writeRange).setValues([dataArray]); // 单行数据需转为二维数组 sendTgMessage(chatId, '数据已成功写入!😎'); } catch(err) { sendTgMessage(chatId, '数据格式不对,请重新输入(比如"张三,25,北京"):'); return; } // 清空状态和临时数据 userProps.deleteProperty(`state_${chatId}`); userProps.deleteProperty(`temp_write_range_${chatId}`); break; } } return ContentService.createTextOutput(JSON.stringify({ok: true})).setMimeType(ContentService.MimeType.JSON); } // 发送Telegram消息的辅助函数 function sendTgMessage(chatId, text) { const botToken = '你的机器人Token'; const url = `https://api.telegram.org/bot${botToken}/sendMessage`; UrlFetchApp.fetch(url, { method: 'POST', contentType: 'application/json', payload: JSON.stringify({ chat_id: chatId, text: text }) }); }
额外注意事项
- 会话超时:可以在存储状态时同时记录时间戳,每次处理消息时检查是否超过设定的超时时间(比如10分钟),如果超时就自动清空状态,避免用户长时间不操作导致状态残留。
- 错误处理:要捕获Sheet操作的异常(比如无效范围、数据格式错误),提示用户重新输入并保持当前状态,提升用户体验。
- 属性大小限制:Properties Service的单个属性值不能超过9KB,如果需要存储大量临时数据,建议用Google Sheet的会话状态表来替代。
内容的提问来源于stack exchange,提问作者luneart




