You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Node.js获取Discord已有频道权限并创建同权限频道

discord.js v13 动态语音频道同权限克隆实现

核心实现逻辑

要复刻频道完整权限,不需要手动计算每一项权限位,直接读取目标频道的权限覆盖缓存即可:

  • 语音频道实例下的permissionOverwrites.cache存储了该频道所有角色、成员的单独权限配置(允许/拒绝的权限位)
  • 创建新频道时直接传入该权限配置数组,同时同步原频道的所属分类、比特率、用户上限等属性,即可生成权限完全一致的新频道
  • 注意修复原代码中querychannelcount函数缺失闭合大括号的语法错误

补全后可运行代码

// 请根据自身项目逻辑引入mysql、logger、数据库连接实例con等依赖
const { VoiceChannel } = require('discord.js');

module.exports = {
    name: "voiceStateUpdate",
    async execute(oldState, newState) {
        try {
            const newStateChannelId = newState.channelId;
            const oldStateChannelId = oldState.channelId;

            // 用户加入语音频道分支
            if (oldStateChannelId === null && newStateChannelId !== null) {
                const joinChannel = newState.channel;
                const emptyChannelCount = joinChannel.parent.children.cache
                    .filter(c => c.type === 'GUILD_VOICE' && c.members.size === 0 && isDynamicChannel(c.id)).size;
                // 空频道不足1个时触发扩容创建
                if (emptyChannelCount < 1) await channelCreate(joinChannel);
                console.log("join " + toString(await querychannelcount(newStateChannelId)));
            } 
            // 用户离开语音频道分支
            else if (oldStateChannelId !== null && newStateChannelId === null) {
                const leaveChannel = oldState.channel;
                // 清理多余空频道,始终仅保留1个空频道
                const emptyChannels = leaveChannel.parent.children.cache
                    .filter(c => c.type === 'GUILD_VOICE' && c.members.size === 0 && isDynamicChannel(c.id));
                if (emptyChannels.size > 1) {
                    emptyChannels.slice(1).forEach(c => c.delete('清理多余空动态频道'));
                }
                console.log("leave " + toString(await querychannelcount(oldStateChannelId)));
            } 
            // 用户切换语音频道分支
            else if (oldStateChannelId !== null && newStateChannelId !== null && oldStateChannelId !== newStateChannelId) {
                const newJoinChannel = newState.channel;
                const emptyChannelCount = newJoinChannel.parent.children.cache
                    .filter(c => c.type === 'GUILD_VOICE' && c.members.size === 0 && isDynamicChannel(c.id)).size;
                if (emptyChannelCount < 1) await channelCreate(newJoinChannel);
                // 处理旧频道侧的空频道清理
                const oldLeaveChannel = oldState.channel;
                const oldEmptyChannels = oldLeaveChannel.parent.children.cache
                    .filter(c => c.type === 'GUILD_VOICE' && c.members.size === 0 && isDynamicChannel(c.id));
                if (oldEmptyChannels.size > 1) oldEmptyChannels.slice(1).forEach(c => c.delete('清理多余空动态频道'));
                
                channelCreate(newState);
                console.log("change " + toString(await querychannelcount(newStateChannelId)));
            } 
            // 麦克风静音/闭麦/开直播等状态变更不做处理
            else if (oldStateChannelId === newStateChannelId) {
                return;
            } 
            else {
                logger.warn("voiceStateUpdate 触发未覆盖的异常分支");
            }
        } catch (error) {
            logger.warn("voiceStateUpdate 事件执行出错");
            console.log(error);
        }
    },
};

// 频道类型校验:判断频道是否属于配置的动态频道组,可结合自身数据库逻辑修改
function isDynamicChannel(channelId) {
    return true;
}

function querychannelcount(channelId) {
    try {
        var sql = "SELECT COUNT(id)*10 FROM channels Where id = ?";
        var Inserts = [channelId];
        sql = mysql.format(sql, Inserts);
        return new Promise((resolve, reject) => {
            con.query(sql, (err, result) => {
                return err ? reject(err) : resolve(result);
            });
        });
    } catch (error) {
        logger.error(`Error performing "SELECT" in database ${cascadingChannels_DB_database} in event voiceStateUpdate.`);
    }
} // 原代码缺失该闭合大括号,已补全

function toString(object) {
    let result = JSON.stringify(object).length - 20;
    return result;
}

async function channelCreate(templateChannel) {
    try {
        if (!(templateChannel instanceof VoiceChannel)) return;
        // 拉取模板频道全量权限配置
        const permissionOverwrites = templateChannel.permissionOverwrites.cache.map(overwrite => {
            return {
                id: overwrite.id,
                type: overwrite.type,
                allow: overwrite.allow.bitfield,
                deny: overwrite.deny.bitfield
            }
        });
        // 新频道命名规则可自行修改
        const newChannelName = `${templateChannel.name} 动态房`;
        // 创建与模板频道配置完全一致的新语音频道
        const newChannel = await templateChannel.guild.channels.create(newChannelName, {
            type: 'GUILD_VOICE',
            parent: templateChannel.parentId,
            bitrate: templateChannel.bitrate,
            userLimit: templateChannel.userLimit,
            position: templateChannel.position + 1,
            permissionOverwrites: permissionOverwrites,
            reason: '动态扩容创建新语音频道'
        });
        // 此处可添加数据库写入逻辑,将新频道ID存入动态频道表
        return newChannel;
    } catch (error) {
        console.log('创建动态频道出错:', error);
    }
}

注意事项

  • 代码中isDynamicChannel函数需要结合自身业务逻辑修改,避免在非动态频道下误触发克隆逻辑
  • 直接读取allow.bitfield和deny.bitfield是discord.js v13版本的正确权限传参方式,不需要手动做权限位转换
  • 空频道清理逻辑已经内置到加入、离开、切换频道的分支中,可保证同分类下动态频道组始终仅保留1个空频道

内容的提问来源于stack exchange,提问作者xCesir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:39:13