
在开发 line bot 时,常见需求是在接收到用户消息后,先通过外部 api(如 openai)生成回复文本,然后希望在文本之后再发送一个相关的贴图。初次尝试时,开发者可能倾向于分两步执行:首先调用 client.replymessage 发送文本,然后再次调用 client.replymessage 发送贴图。然而,这种做法通常会导致第二次 replymessage 调用失败,并返回 httperror: request failed with status code 400,错误信息中包含 bad request。
错误根源:replyToken 的一次性使用原则
LINE Messaging API 中的 replyToken 是一个非常关键的标识符,它代表了对特定用户事件的唯一回复机会。根据 LINE 的设计,每个 replyToken 只能被用于一次 replyMessage 调用。这意味着,一旦你使用 replyToken 发送了第一条消息(例如文本),该 replyToken 就会立即失效。随后的任何尝试使用同一个 replyToken 发送消息的请求都将被视为无效,从而导致 400 Bad Request 错误。
在原始的代码实现中,handleEvent 函数首先发送文本消息:
await client.replyMessage(event.replyToken, {
type: 'text',
text: reply
});紧接着,它尝试发送贴图消息:
await sendStickerMessage(event.replyToken);
由于这两次调用使用了相同的 event.replyToken,第二次调用必然会失败。
LINE Messaging API 提供了在单次 replyMessage 调用中发送多条消息的能力。你只需要将所有要发送的消息(包括文本、贴图、图片、视频等)封装成一个数组,然后将这个数组作为 client.replyMessage 的第二个参数。
核心改动点:
以下是基于原始问题和解决方案优化后的完整代码,展示了如何正确地在一次回复中发送文本和贴图。
'use strict';
// ########################################
// 初始化与配置
// ########################################
// 加载所需模块
const line = require('@line/bot-sdk');
const openai = require('openai');
const express = require('express');
const PORT = process.env.PORT || 3000;
// LINE Bot 配置
const config = {
channelSecret: process.env.LINE_CHANNEL_SECRET || 'YOUR_CHANNEL_SECRET', // 建议从环境变量获取
channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN || 'YOUR_CHANNEL_ACCESS_TOKEN' // 建议从环境变量获取
};
// 创建 LINE Bot 客户端
const client = new line.Client(config);
// OpenAI GPT 配置
const gptConfig = new openai.Configuration({
organization: process.env.OPENAI_ORGANIZATION_ID || "YOUR_ORGANIZATION_ID", // 建议从环境变量获取
apiKey: process.env.OPENAI_API_KEY || 'YOUR_API_KEY', // 建议从环境变量获取
});
const gpt = new openai.OpenAIApi(gptConfig);
/**
* 调用 OpenAI API 生成聊天回复
* @param {Array<Object>} userMessages 用户消息数组,包含角色和内容
* @returns {Promise<Object>} OpenAI API 的完成结果
*/
const makeCompletion = async (userMessages) => {
const prompt = {
role: 'system',
content: '你是一位技艺精湛的鬼故事讲述者。请根据用户提供的关键词创作一个鬼故事。'
};
userMessages.unshift(prompt); // 将系统提示添加到消息列表的开头
console.log('发送给 OpenAI 的消息:', userMessages);
return await gpt.createChatCompletion({
model: 'gpt-3.5-turbo',
messages: userMessages,
temperature: 0.2, // 降低温度以获得更稳定的故事
n: 1
});
};
/**
* 处理 LINE 消息事件
* @param {Object} event LINE Webhook 事件对象
* @returns {Promise<null>}
*/
async function handleEvent(event) {
// 忽略非文本消息类型
if (event.type !== 'message' || event.message.type !== 'text') {
return Promise.resolve(null);
}
const userMessage = [
{
role: 'user',
content: event.message.text
}
];
try {
// 调用 ChatGPT API 生成回复
const completion = await makeCompletion(userMessage);
const replyText = completion.data.choices[0].message.content;
// 使用 sendCombinedMessages 函数发送文本和贴图
return sendCombinedMessages(event.replyToken, replyText);
} catch (error) {
console.error('处理消息时发生错误:', error);
// 可以在这里发送一个错误提示给用户
return Promise.resolve(null);
}
}
/**
* 发送组合消息(文本和贴图)
* @param {string} replyToken 用于回复的令牌
* @param {string} text 要发送的文本内容
* @returns {Promise<Object>} LINE API 的回复结果
*/
async function sendCombinedMessages(replyToken, text) {
try {
const textMessage = {
type: 'text',
text: text
};
const stickerMessage = {
type: 'sticker',
packageId: '446', // 替换为你的贴图包 ID
stickerId: '2027' // 替换为你的贴图 ID
};
// 将文本消息和贴图消息放入一个数组中,一次性发送
return client.replyMessage(replyToken, [textMessage, stickerMessage]);
} catch (error) {
console.error('发送组合消息时发生错误:', error.message);
if (error.response) {
console.error('LINE API 错误详情:', error.response.data);
}
// 可以在这里根据错误类型进行更细致的处理
}
}
// ########################################
// Express 服务器设置
// ########################################
const app = express();
// 根路径 GET 请求,用于健康检查或简单提示
app.get('/', (req, res) => res.send('Hello LINE BOT! (HTTP GET)'));
// Webhook POST 请求处理
app.post('/webhook', line.middleware(config), (req, res) => {
if (req.body.events.length === 0) {
res.send('Hello LINE BOT! (HTTP POST)');
console.log('收到验证事件!');
return;
} else {
console.log('收到事件:', req.body.events);
}
// 并行处理所有事件
Promise.all(
req.body.events.map((event) => {
if (event.type === 'message' && event.message.type === 'text') {
return handleEvent(event);
}
// 如果需要处理其他消息类型(如贴图消息),可以在这里添加逻辑
// else if (event.type === 'message' && event.message.type === 'sticker') {
// return handleMessageEvent(event); // 假设有处理贴图消息的函数
// }
else {
return null;
}
})
).then((result) => res.json(result));
});
// 启动 Express 服务器
app.listen(PORT, () => {
console.log(`Express 服务器正在端口 ${PORT} 上运行...`);
});handleEvent 函数中的调用方式变更: 原始代码:
await client.replyMessage(event.replyToken, { type: 'text', text: reply });
await sendStickerMessage(event.replyToken);修改后:
return sendCombinedMessages(event.replyToken, replyText);
现在,handleEvent 不再直接发送消息,而是调用一个新的辅助函数 sendCombinedMessages,并将 replyText 传递过去,确保所有消息在一次 replyMessage 调用中完成。
新增 sendCombinedMessages 函数: 这个新函数负责构建一个消息数组,然后使用 client.replyMessage 一次性发送。
async function sendCombinedMessages(replyToken, text) {
try {
const textMessage = {
type: 'text',
text: text
};
const stickerMessage = {
type: 'sticker',
packageId: '446', // 贴图包 ID
stickerId: '2027' // 贴图 ID
};
// 将多个消息对象放入一个数组中
return client.replyMessage(replyToken, [textMessage, stickerMessage]);
} catch (error) {
console.error('发送组合消息时发生错误:', error.message);
if (error.response) {
console.error('LINE API 错误详情:', error.response.data);
}
}
}这里 [textMessage, stickerMessage] 就是一个消息数组,它包含了两种不同类型的消息。LINE API 会按数组顺序发送这些消息。
环境变量的使用: 为了提高安全性和可维护性,建议将敏感信息(如 channelSecret, channelAccessToken, OPENAI_API_KEY 等)存储在环境变量中,而不是硬编码在代码里。示例代码已更新为从 process.env 读取配置,并在未设置时提供默认占位符。
通过上述修改,你的 LINE Bot 将能够稳定地在一次回复中同时发送文本消息和贴图,提升用户体验,并避免因 replyToken 重复使用而导致的 400 错误。
以上就是LINE Bot 多消息类型回复:文本与贴图的组合发送指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号