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

discord.js机器人分配用户角色时报50013 Missing Permissions错误

问题现象

基于discord.js开发的Discord服务器自动分配角色机器人,运行时抛出权限不足错误,相同代码1年前可正常运行,近期无改动突然失效。
已知前置条件:

  • 机器人已被授予业务逻辑所需的全部权限
  • 代码逻辑未尝试修改服务器所有者、角色层级高于机器人的用户的角色
报错信息
/app/node_modules/discord.js/src/client/rest/RequestHandlers/Sequential.js:85
               new DiscordAPIError(res.request.path, res.body, res.request.method) : err);
               ^
 
 DiscordAPIError: Missing Permissions
     at /app/node_modules/discord.js/src/client/rest/RequestHandlers/Sequential.js:85:15
     at /app/node_modules/snekfetch/src/index.js:215:21
    at processTicksAndRejections (node:internal/process/task_queues:96:5) {
   path: '/api/v7/guilds/715245634218885131/members/235148962103951360',
   code: 50013,
  method: 'PATCH'
 }
相关实现代码
//basic constants and functions
var checked;
var checking = [];
const Discord = require("discord.js");
const {usernameorid, token,usernamesp, roles, apiKey, spreadsheetId, range, unremoveableroles, refreshrate} = require("./config/discord.json");
const { google } = require("googleapis");

  const connection = google.sheets({
  version: "v4",
  auth: apiKey
});


const client = new Discord.Client();

let oldRows = [];


//checking for errors
const logError = error => {
  console.error("error log " + new Date());
  console.error(error);
};

//the spreadsheet fetching
const fetchRows = async (spreadsheetId, range, sheetsConnection) => {
  try {
    const response = await sheetsConnection.spreadsheets.values.get({
      spreadsheetId: spreadsheetId,
      range: range
    });
    //fetching all data
    const rows = response.data.values;








    //dividing the data between different array using while loop
    var i = 0;
    var k=0;
   


    while (i < rows.length) {
 
   
      const checkrow="";
      
   
      
      
      const rows123= rows[i]
 
      
      
   
        
//finally putting all roles together

      
      
const therolenames = rows123[1]+","+rows123[2]+","+rows123[3]+","+rows123[4];

    
      assignRoles(rows123[0].trim(), therolenames.trim());
  


         rows123[0]==null;
   rows123[1]==null;
   rows123[2]==null;
   rows123[3]==null;
   rows123[4]==null;   rows123[1]==null;
   roles[1]==null;
   roles[2]==null;
   roles[3]==null;
   roles[4]==null;
   
      
    therolenames==null;
   roles.length=0;   
rows123.length=0;
i++;
k++;
       
      }
   
    return rows;
  } catch (error) {
    logError(error);
  }
};

const assignRoles = async (usernames, roleNames) => {

   var rolenames1 = roleNames.split(",");

  try {
    const guildMembers = client.guilds.array()[0].members.array();
 
    //number of all members in the group
 
    
    const guildRoles = client.guilds.array()[0].roles.array();
    // deleting unremovable roles from the list
  const Roles2 = guildRoles.filter(Role => !unremoveableroles.includes(Role.name));
    // * getting Role instances of role names
    const removedRoles = guildRoles.filter(Role => unremoveableroles.includes(Role.name));
    const Roles = guildRoles.filter(Role => roleNames.includes(Role.name));
   //role number of all members in the group
   

    roleNames.length=0;
    // * getting GuildMember instances of usernames and setting roles
    guildMembers.forEach(async member => {
     
      const  username= member.user.username + "#" + member.user.discriminator;
    
 
  //check if the first column of spreadsheet matches the userid/username of discord

var UNcheck;
      
      if (usernameorid=="UN"){
  
   UNcheck=username
  
  }else{
    
  UNcheck=member.id
    
    
  }

  
  if (usernames.includes(UNcheck))  {
        
        const notAssignedRoles = [];
        
       
      
        
        
        
        
        Roles.forEach(role => {
       
         //if (!member.roles.array().includes(Roles)) {
         //if (!guildRoles.includes(Roles)) {
          
          
      
      checked="no"
          
           
                  //Check whether the roles (discord) are in roles (spreadsheet)
          
          
        
              if (Roles.includes(member.roles.array()[1])|| removedRoles.includes(member.roles.array()[1])|| member.roles.array()[1]==null){
             
              }else{checked="yes" }    
                if (Roles.includes(member.roles.array()[2]) || removedRoles.includes(member.roles.array()[2]) || member.roles.array()[2]==null){
              
               }else{checked="yes"; }    
                    if (Roles.includes(member.roles.array()[3]) || removedRoles.includes(member.roles.array()[3])|| member.roles.array()[3]==null){
                  
                 }else{checked="yes"; }    
                   if (Roles.includes(member.roles.array()[4])|| removedRoles.includes(member.roles.array()[4]) || member.roles.array()[4]==null){
           
                   }else{checked="yes"; }    
          
                    
                           //Check whether the roles (Spreadsheet) are in roles (Discord)
           
                    
              if (member.roles.array().includes(Roles[0])|| removedRoles.includes(Roles[0]) || Roles[0]==null){
              
              }else{checked="yes"; }    
               if (member.roles.array().includes(Roles[1]) || removedRoles.includes(Roles[1])|| Roles[1]==null){
          
             
                  }else{checked="yes"; }    
                        if (member.roles.array().includes(Roles[2]) || removedRoles.includes(Roles[2]) || Roles[2]==null){
            
                      }else{checked="yes"; }    
                             if (member.roles.array().includes(Roles[3])|| removedRoles.includes(Roles[3]) || Roles[3]==null){
             
                          }else{checked="yes"; }    
             
     
          
                      if (checked=="yes"){ 
                        notAssignedRoles.push(role);
                      }

        });
        
        if (notAssignedRoles.length > 0) {
        
        
     
          
        
     
          
          await  member.removeRoles(Roles2); member.addRoles(Roles);
 
          
          
        
        
      
          console.log(
            "Assigned " +
              notAssignedRoles.map(role => role.name) +
              " to " +
              username +  " ["+member.id+"] on " + new Date().toString()
          );
        } else {
              
            if (Roles[0]==null && Roles[1]==null && Roles[2]==null && Roles[3]==null) {
             member.removeRoles(Roles2);
       console.log(username + " ["+member.id+"] has all roles removed on"+ new Date().toString());     
          }else{
          
          console.log(username + " ["+member.id+"] already has all the roles assigned, checked on "+ new Date().toString());
          }
          }
      }
    });
  } catch (err) {
    logError(err);
  }
};






const extractNewEntries = (oldRows, rows) => {
  let newRows = [];
  if (rows.length > oldRows.length) {
    newRows = rows.slice(oldRows.length);
   
  }
  newRows.forEach(row => {
    oldRows.push(row);
    
  });
  return newRows;
};

const extractDiscordIDs = rows => {
  return rows.map(user => user[0]);
  console.log(rows.map(user => user[0]));
};

client.once(
  "ready",
  () => {
    console.log("Bot started with these settings:");
    console.log("• Spreadsheet ID: ");
    console.log("• Range: " + range);
    console.log("• Roles: " + roles.join(", "));
    setInterval(async () => {
      console.log("\nChecked for new entries on " + new Date().toString());
      let rows;
      try {
        rows = await fetchRows(spreadsheetId, range, connection);

        const newEntries = extractNewEntries(oldRows, rows);
        if (newEntries.length > 0) {
          console.log(`Found ${newEntries.length} new entries`);
          const usernames = extractDiscordIDs(newEntries);
          
        } else {
          console.log("No new entries");
        }
      } catch (err) {
        if (rows === undefined) console.error("Google Sheet is empty");
        logError(err);
        rows = [];
      }
    }, refreshrate); // refresh rate: 60000 milliseconds == 1 minute
  },
  logError
);

client.on("error", logError);

const start = async () => {
  try {
    await client.login(token);
  } catch (err) {
    logError(err);
    setTimeout(start, 30000);
  }
};

start();

client.on("disconnect", start);

module.exports = {
  fetchRows,
  assignRoles,
  extractNewEntries,
  extractDiscordIDs
};



const express = require("express");
const fs = require("fs");
const app = express();

app.use(express.static("public"));
const listener = app.listen(process.env.PORT, function() {
  console.log("Your app is listening on port " + listener.address().port);
});
故障根因
  1. 依赖版本与API版本过时
    项目使用的discord.js为v11版本,调用的是Discord API v7接口,该版本API早已停止维护并下线,Discord侧对旧接口的权限校验规则、数据返回逻辑均已变更,旧接口调用会出现非预期的权限拒绝。从报错路径/api/v7/可以直接确认该问题。

  2. 缺少特权网关意图配置
    Discord从2021年开始强制要求所有操作服务器成员数据的机器人开启「服务器成员意图」特权开关,代码初始化Client时既没有声明需要的intents参数,也没有在Discord开发者后台开启对应特权开关,导致机器人能获取到的成员缓存、角色数据不完整,操作不存在/不匹配的对象时触发权限错误。

  3. 角色层级校验缺失
    代码中Roles2集合包含了服务器内所有不在不可移除列表里的角色,没有校验这些角色的排序位置是否低于机器人自身的最高角色。Discord权限规则明确要求:机器人只能操作层级低于自身最高角色的其他角色,只要待移除/添加的角色列表里有任意一个角色层级高于机器人,哪怕操作对象是普通用户,接口也会直接返回50013权限不足错误。

  4. 目标服务器选择逻辑bug
    代码通过client.guilds.array()[0]取缓存中第一个服务器作为操作目标,如果机器人加入了多个服务器,会直接操作非目标服务器,在非目标服务器内机器人没有配置对应角色和权限,自然会抛出权限错误。

修复方案
  • 升级discord.js到v14稳定版本,适配当前官方支持的Discord API v10接口,替换旧版本废弃的array()、removeRoles()、addRoles()等方法,改用新版的cache、member.roles.set()等标准接口。
  • 初始化Client时传入需要的intents配置,同时到Discord开发者后台的机器人设置页,开启特权网关意图分类下的服务器成员意图、消息内容意图开关。
  • 不要用数组下标取服务器,直接通过配置的目标服务器ID获取Guild实例,避免操作错服务器。
  • 操作角色前先过滤掉所有层级大于等于机器人最高角色的角色,不要尝试操作这类无权限的角色。
  • 替换原有先全量移除角色再添加角色的逻辑,直接用member.roles.set()方法一次性设置用户最终的角色列表,减少接口调用次数和权限触发概率。

内容的提问来源于stack exchange,提问作者Suprin Aziz Talpur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 16:28:03