Discord.js: Guild returns empty collections (members, roles, channels etc.)

Created on 18 Feb 2019  Â·  13Comments  Â·  Source: discordjs/discord.js

Problem

In version 11.4.2 (latest stable one) I'm fetching a guild (any guild I tried) and I receive only the first level information. Anything that comes in nested objects (channels, members etc.) is an empty collection.

I tried Master branch and this issue seems to be fixed. But it would be nice for the fix to take place on the stable version as well.

Expected Behavior

To return non-empty collections in regards to any nested object in the a guild object.

This was not a behavior I had encountered in my past projects.

Code

const discord = require('discord.js');

const client = new discord.Client();
client.login('token');

const guild = client.guilds.get('guildid');

console.log(guild);
console.log(guild.channels, guild.members, guild.roles, guild.emojis);
// output
{
members: { }, // empty
channels: { }, // empty
roles: { }, // empty
presences: { }, // empty
deleted: false,
available: true,
id: "449894715294474692",
name: "Guild Name",
icon: null,
splash: null,
region: "eu-central",
memberCount: 3,
large: false,
features: [ ],
applicationID: null,
afkTimeout: 300,
afkChannelID: null,
systemChannelID: "427694145034819594",
verificationLevel: 0,
explicitContentFilter: 0,
mfaLevel: 0,
joinedTimestamp: 1548127935779,
defaultMessageNotifications: "ALL",
ownerID: "871420893962024974",
_rawVoiceStates: { },
emojis: { } // empty
}
{ }
{ }
{ }
{ }

Further details:

  • discord.js: v11.4.2
  • Node.js version: v10.10.0
  • Operating system: Ubuntu 18.04
  • Priority this issue should have – please be realistic and elaborate if possible: Medium-High
    (Since it doesnt display vital information about the server, unless it's my mistake)
  • [x] I have also tested the issue on latest master, commit hash: 4009986bc8bd1ccf0bdbd0b5135c1bde78839ed2
    On master branch it works as it is expected to work. It's on 11.4.2 stable where the problem lies.
invalid

Most helpful comment

Anything that comes in nested objects (channels, members etc.) is an empty collection.

The output you provided shows empty objects, not Collections, that's an important distinction.

Code [...]

That code can and will never produce that output for two reasons:

  • As @almostSouji already pointed out, the client will most likely not be ready yet. (No guilds there at all)
  • Even if you got lucky with that race condition and the guild is already there, the channels, members, etc will _never_ be an empty object; They would be empty collections then. (See distinction earlier)

The Cause

Since it's not exactly clear how you got that output and that comment seems like you are sending those as-is via express (see the previous comment), I am going to assume you are looking at the json response from express.

Looking at express:
Calling Response#send with an object will internally call Response#json which will just JSON.stringify the resource in the end.

JSON.stringifying a Map will in the very most cases result in {}. (This is what you are seeing)
("very most" because manually adding enumerable properties to it is possible)


Why does it work on master / v12 then?

It's "working" there because we added toJSON to pretty much everything. (See #1859; Regarding to the toJSON behaviour in general see MDN)

Collections now serialize to an array of flattened values.

In your Case

Util.flatten internally called from Guild#toJSON and then from Base#toJSON will use a valueOf method if available.
GuildMember, GuildChannel, and GuildEmoji extend Base which defines Base#valueOf returning its id.

That's why your empty objects on 11.4.2 and 11.4-dev are now arrays of ids on master / v12.


A Solution?

Depending on how dynamically you need different objects and their properties:

  • Define your own serialization of objects:
    { name: guild.name, members: guild.members.keyArray(), etc}
  • Use the master branch (and possibly lock to a commit).
  • Or something else I couldn't think of at the moment.

All 13 comments

Trying to access client properties without the client being logged in results in empty collections.
This also doesn't work on master, you might use a different (valid) approach on your master build.

Edit: To make sure the client is logged in, put your code inside an event callback. If you want it to happen directly when the bot logs in "ready" is the event of choice

For future requests:
If you need help with discord.js installation or usage, please go to the discord.js Discord server instead:
https://discord.gg/bRCvFy9
This issue tracker is only for bug reports and enhancement suggestions.

No I actually used exactly the same approach with both of them, except that I just replaced all the content of the old module in node_modules/discord.js with the new content and then I run yarn/npm install in the folder to install the modules.

But I did exactly the same thing with both of them and I got two different results.

edit 1

// actual source code - same approach for both 11.4.2 and Master branch
if (client) {
    const guildID: string = req.params.guildID;
    const guild: Guild | undefined = client.guilds.get(guildID);
    if (guild) {
      res.send({channels: guild.channels});
    } else {
      res.send('Guild was undefined');
    }
  }

edit 2

Also if I was not logged in, I dont think I would get a result of the guilds at all, would I? Doesnt Discord API require a Bearer or a Bot token as a Authorization header?

I misread a small portion of that, however the solution still persists
Maybe you were lucky enough that this specific guild/the guild list in question is received relatively quickly seeing that you call the login method before execution of your other code.

The guild data seems to not have arrived though, so the deeper collections are not yet populated (as they are initialized as empty collections).

Please try putting your code into an event callback as mentioned earlier (again, "ready" is appropriate if you want the code to execute immediately after the client is fully logged in).

Yep. but I just told you that I've tested this, and multiple times, on the Master branch and it works perfectly with exactly the same code. It doesnt work with 11.4.2 only. Isn't this a clue that it's probably a bug?

The client has fully logged in. And to be sure of that, I tested this multiple times after delays and delays.

Let me rephrase in a clean listed way

  • I tested this with the master branch, and it worked even the 5th time I tested it, after testing the 11.4.2 the same amount of times
  • I suppose that the client has logged in after a big delay and I've set login callbacks.

I can reproduce neither the behavior of stable nor master, both outputs are equal and what i expected when first seeing your code (hence my first response being a slight miss of the overall situation)

C:\Users\souji\Documents\projects\djs-testing\stable\index2.js:10
console.log(guild.channels, guild.members, guild.roles, guild.emojis);
                  ^

TypeError: Cannot read property 'channels' of undefined
C:\Users\souji\Documents\projects\djs-testing\master\index2.js:10
console.log(guild.channels, guild.members, guild.roles, guild.emojis);
                  ^

TypeError: Cannot read property 'channels' of undefined

Yeah probably because you have not a guild in your bot?
In your case it says that it cannot find the guild at all.
In my case, where I have logged in with a proper bot token and that bot has access to 1-2 channels, It finds the guild, it outputs the information about the guild (name, id, server etc.) but not the rest of the objects.

Your code error says it all, it can't find channels because guild is undefined.

this bot is on 3 guilds, as such your claim is invalid.
it can not find the guild because (as mentioned in the initial answer) at the point of logging the client is not yet logged in, meaning the client collections (example here client.guilds) will not be populated (as they are initialized as empty collection)

Granted I don't know how it works on your end, but the output above is the expected and correct output of the library assuming this exact code.

The provided solution (putting your code into event callbacks) is the correct and expected way of handling code for your bot for all versions of this library.

Ah I see what you did, pardon me.
You tested the first code, which was without callbacks.

You could test it again and make a request after the client has logged in.
In my instance it has logged in, or else it wouldn't provide anything at all.

That is how I do the things in my server

// /src/index.ts
/*
 * Web server
 */
// Dependencies
import * as express from "express";
import * as routes from './routes';
import { server } from '../config/server';
import * as client from './services/client';

// Express app
const app = express();

// Client Logging in
client.login().then(client => {
  // Routes
  app.use(routes);
});


// Start Webserver
const PORT = server.port || 3001;
app.listen(PORT, () => {
  console.log(`Server has started on port ${PORT}`);
});
// /src/services/client.ts
import axios from 'axios';
import { server as serverConfig } from '../../config/server';
import * as Discordjs from 'discord.js';

// Discord Client
let client: Discordjs.Client;

// Login to the bot
async function login(): Promise<Discordjs.Client> {
  // Request Bot Token
  return new Promise<Discordjs.Client>((resolve, reject) => {
    axios.get(serverConfig.apiGatewayURL + '/discord/token')
      .then(data => {
        // Bot Token
        const token: string = data.data as unknown as string;
        // Instantiate client
        client = new Discordjs.Client();
        // Login
        if (client !== undefined && client instanceof Discordjs.Client) {
          client.login(token)
            .then(value =>{
              resolve(client);
            })
            .catch(err => {
              reject(err);
            });
        } else {
          reject('Client was undefined');
        }
      })
      .catch(err => {
        reject(err);
      });
  });
}

export { login, client };

And this still happens. It's not about the client haven't logged in, or else it just wouldn't find the objects at all like in your case, right?
And again, this works in the Master branch, but not in the Stable version.

Anything that comes in nested objects (channels, members etc.) is an empty collection.

The output you provided shows empty objects, not Collections, that's an important distinction.

Code [...]

That code can and will never produce that output for two reasons:

  • As @almostSouji already pointed out, the client will most likely not be ready yet. (No guilds there at all)
  • Even if you got lucky with that race condition and the guild is already there, the channels, members, etc will _never_ be an empty object; They would be empty collections then. (See distinction earlier)

The Cause

Since it's not exactly clear how you got that output and that comment seems like you are sending those as-is via express (see the previous comment), I am going to assume you are looking at the json response from express.

Looking at express:
Calling Response#send with an object will internally call Response#json which will just JSON.stringify the resource in the end.

JSON.stringifying a Map will in the very most cases result in {}. (This is what you are seeing)
("very most" because manually adding enumerable properties to it is possible)


Why does it work on master / v12 then?

It's "working" there because we added toJSON to pretty much everything. (See #1859; Regarding to the toJSON behaviour in general see MDN)

Collections now serialize to an array of flattened values.

In your Case

Util.flatten internally called from Guild#toJSON and then from Base#toJSON will use a valueOf method if available.
GuildMember, GuildChannel, and GuildEmoji extend Base which defines Base#valueOf returning its id.

That's why your empty objects on 11.4.2 and 11.4-dev are now arrays of ids on master / v12.


A Solution?

Depending on how dynamically you need different objects and their properties:

  • Define your own serialization of objects:
    { name: guild.name, members: guild.members.keyArray(), etc}
  • Use the master branch (and possibly lock to a commit).
  • Or something else I couldn't think of at the moment.

@SpaceEEC Thanks thanks thanks!
Very precise, on point and organized answer. It solved all of my problems!

Indeed, the problem was the fact that the objects were serialized.

I replaced this non working code

const categories = guild.channels.filter((channel) => channel.type === 'category');
res.send({categories});

With this code

const categories = guild.channels.filter((channel) => channel.type === 'category');
res.send({categories: categories.keyArray()});

And now it works!

I'm so happy that I understand the problem now and why it works in Master. What a relief :D

But it wasn't a racing problem though, even though the first sample code had a racing problem indeed. Although, I provided it just for sample for my problem, falsly.

Note: I had the same issue of empty client.guilds. My issue was different. It was due to an odd first run of the bot where I suspect I used an older key for a similar bot for the new bot, and invited the wrong bot. It was strange because even after correcting the token and inviting the proper bot I still had the empty guilds on start. I was not able to resolve it until I deleted the bot and remade it.

I'm also encountering this issue trying to create a Discord bot. Following the instructions here, I'm unable to get the list of roles in my Discord server. I'm just trying to output a list of all the users in a role.

const Discord = require('discord.js');
const client = new Discord.Client();

client.on('ready', () => {
console.log(`Logged in as ${client.user.tag}!`);
});

client.on('message', msg => {
    var role = message.guild.roles.get(args[0]);
    if (role != null) {
        var output = "In role:\n";
                output += members.map(member => "<@" + member.user.id + ">");
        message.channel.send( output );
    } else {
        message.channel.send('Invalid role ID ' + args[0] + '.');
    }
 });

client.login('mytokenhere');

UPDATE: It seems Discord changed the auth token for my bot? I updated my auth token, fixed my code up a little, and now it works.

I stumbled upon a similar thing today; reason is documented in #3924; I was missing the GUILDS intent. This comment is mostly for google searches for "discord.js channels empty" and similar.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DatMayo picture DatMayo  Â·  3Comments

ghost picture ghost  Â·  3Comments

tom-barnes picture tom-barnes  Â·  3Comments

iCrawl picture iCrawl  Â·  3Comments

LLamaFTL picture LLamaFTL  Â·  3Comments