Discord.js Twitch直播embed报错TypeError 无法读取channel属性
Discord Twitch直播通知机器人报错排查与修复方案
问题现象
开发Discord平台Twitch直播通知功能时先后出现两类异常:
- 初始版本触发
TypeError: Cannot read properties of undefined (reading 'channel')报错,嵌入消息仅展示静态封面图,无法正常加载直播流跳转链接 - 调整代码后消息可正常发送,但机器人仍会随机报错崩溃
报错根因
两个版本的异常均由空值访问、逻辑疏漏导致,具体问题点如下:
- 初始版本的核心错误是多余属性访问:
channels.cache.find()返回值本身就是频道对象,额外访问不存在的.channel属性直接触发第一类报错 - 两个版本共同问题是缺少空值校验:如果频道未加载到缓存、机器人无频道访问权限、事件触发时服务器缓存未就绪,查询到的频道对象为
undefined,直接调用send方法就会导致进程崩溃 - 初始版本消息结构错误:将普通@提醒文本和Embed对象一同传入
embeds数组,会导致消息解析异常,直播链接无法正常渲染 - 缺少开播去重逻辑:直播过程中用户在线状态会多次更新(比如切换游戏、修改个人状态),没有去重判断会导致重复发通知,高频调用接口也容易触发限流失效
- 未过滤非Twitch直播:Discord的STREAMING活动类型支持YouTube等多个平台,直接按Twitch规则拼接预览图会出现资源加载失败
修复方案
修复后完整可运行代码
// 提前定义通知频道ID,避免硬编码散落在业务逻辑中 const ANNOUNCE_CHANNEL_ID = "993751231799500871"; // 记录已发送开播通知的用户ID,避免重复推送 const liveUserMap = new Map(); bot.on("presenceUpdate", async (oldPresence, newPresence) => { // 前置空值校验,跳过缓存不完整的状态更新事件 if (!newPresence?.guild || !newPresence?.user || !newPresence?.activities) return; // 筛选新状态中Twitch平台的直播活动 const activeTwitchStream = newPresence.activities.find(act => { return act.type === "STREAMING" && act.url?.includes("twitch.tv/"); }); // 校验旧状态,判断是否为刚开播场景 const existedStream = oldPresence?.activities?.find(act => { return act.type === "STREAMING" && act.url?.includes("twitch.tv/"); }); // 非新开播场景直接跳过 if (!activeTwitchStream || existedStream) return; try { // 优先从缓存取频道,缓存未命中时主动调用接口拉取,解决缓存缺失导致的undefined问题 const announceChannel = newPresence.guild.channels.cache.get(ANNOUNCE_CHANNEL_ID) || await newPresence.guild.channels.fetch(ANNOUNCE_CHANNEL_ID); // 校验频道有效性和机器人发送权限 if (!announceChannel?.isText() || !announceChannel.permissionsFor(bot.user).has("SEND_MESSAGES", "EMBED_LINKS")) { console.error("通知频道不存在或缺少发送/嵌入链接权限"); return; } // 提取纯净Twitch用户名,处理带参数的直播链接 const twitchUsername = activeTwitchStream.url.split("twitch.tv/").pop().split("?")[0]; // 构造消息Embed,使用Twitch官方品牌色 const streamEmbed = new Discord.MessageEmbed() .setColor("#9146FF") .setTitle(`${newPresence.user.tag} 正在Twitch直播!`) .setURL(activeTwitchStream.url) .setDescription(`**直播已开始**\n${activeTwitchStream.details || "快来围观吧~"}`) // 封面地址加时间戳避免CDN缓存旧图 .setImage(`https://static-cdn.jtvnw.net/previews-ttv/live_user_${twitchUsername}-1920x1080.jpg?t=${Date.now()}`) .setTimestamp(); // 正确拆分消息结构:普通文本传content字段,Embed对象传embeds字段 await announceChannel.send({ content: `@everyone ${newPresence.user.tag} 正在Twitch开播啦,快来看!\n${activeTwitchStream.url}`, embeds: [streamEmbed] }); // 标记用户已发送开播通知 liveUserMap.set(newPresence.user.id, true); // 监听下播事件,清除标记,下次开播可正常推送 const offlineCheck = (oldState, newState) => { if (newState.user.id !== newPresence.user.id) return; const isStillStreaming = newState.activities?.some(act => act.type === "STREAMING" && act.url?.includes("twitch.tv/")); if (!isStillStreaming) { liveUserMap.delete(newPresence.user.id); bot.off("presenceUpdate", offlineCheck); } }; bot.on("presenceUpdate", offlineCheck); } catch (err) { console.error("发送直播通知失败:", err); } });
关键修复点说明
- 所有可能为空的取值都增加可选链
?.做前置校验,避免空值访问直接导致进程崩溃 - 频道查询增加缓存未命中的主动拉取逻辑,同时校验频道类型和机器人操作权限
- 修正消息传参结构,解决链接无法渲染、消息发送失败问题
- 增加开播状态去重逻辑,避免直播过程中反复发送通知
- 过滤非Twitch平台的直播活动,避免预览图拼接错误
- 所有异步逻辑增加try/catch错误捕获,即使发送失败也不会导致机器人退出
- 封面地址增加时间戳参数,避免CDN缓存返回历史静态图
- 移除初始版本中多余的
.channel属性访问,直接在频道对象上调用发送方法
内容的提问来源于stack exchange,提问作者Joe_42o
相关产品推荐
相关产品推荐

