ClientUser banner is undefined when force fetching on ready

I decided to open a post because I asked in d.js help and go no reply and i then bumped the message 2+hrs later and still no reply so i asumed no one active had an idea
// ----------------------------
// on ready:
await client.users.fetch(client.user.id, { cache: true, force: true });
// I have also tried `await client.user.fetch(true);

// ----------------------------
// in command:
client.user.bannerURL({ extension: 'png' })
// ----------------------------
// on ready:
await client.users.fetch(client.user.id, { cache: true, force: true });
// I have also tried `await client.user.fetch(true);

// ----------------------------
// in command:
client.user.bannerURL({ extension: 'png' })
When I ran my command the bannerURL is undefined when it should not as I am force fetching and caching the client user once ready I moved the fetch into my command and it worked however I prefer to keep it in my ready event so I only have to do it one time. I don't understand how the banner could still be undefined I'm using latest discord.js v14.15.2
19 Replies
d.js toolkit
d.js toolkit2mo ago
- What's your exact discord.js npm list discord.js and node node -v version? - Not a discord.js issue? Check out #other-js-ts. - Consider reading #how-to-get-help to improve your question! - Explain what exactly your issue is. - Post the full error stack trace, not just the top part! - Show your code! - Issue solved? Press the button!
duck
duck2mo ago
the fact that it successfully fetches when executing as part of a command suggests that the issue has more to do with your ready listener or some other logic preventing this from executing properly client.user.fetch() and what you have currently should both be fine
Frank
Frank2mo ago
Im not sure how the ready event could be an issue though as everything else works on it. i also fetch my account on ready so i can display developer info without manual putting it in
duck
duck2mo ago
well it'll definitely be hard to give you any further information with just the code shown
Frank
Frank2mo ago
theres not really much to show ready.js
client.once(Events.ClientReady, async me => {
await me.users.fetch(me.user.id, { force: true, cache: true });
// await me.user.fetch(true);

await client.ready(); // I originally tried fetching the client in this method and didn't work so tried fetching in the event and still didn't work
});
client.once(Events.ClientReady, async me => {
await me.users.fetch(me.user.id, { force: true, cache: true });
// await me.user.fetch(true);

await client.ready(); // I originally tried fetching the client in this method and didn't work so tried fetching in the event and still didn't work
});
command:
import { SlashCommandBuilder, type ChatInputCommandInteraction } from 'discord.js';
import type { Command } from '../../../types/Command.js';
import type MyClient from '../../Client';

export default {
data: new SlashCommandBuilder()
.setName('test')
.setDescription('test'),
/**
* @param client -
* @param interaction -
*/
async execute(client: MyClient, interaction: ChatInputCommandInteraction) {
const hash = interaction.client.user.banner;
const url = interaction.client.user.bannerURL({ extension: 'png' });

console.log(hash, url)
}
} satisfies Command;
import { SlashCommandBuilder, type ChatInputCommandInteraction } from 'discord.js';
import type { Command } from '../../../types/Command.js';
import type MyClient from '../../Client';

export default {
data: new SlashCommandBuilder()
.setName('test')
.setDescription('test'),
/**
* @param client -
* @param interaction -
*/
async execute(client: MyClient, interaction: ChatInputCommandInteraction) {
const hash = interaction.client.user.banner;
const url = interaction.client.user.bannerURL({ extension: 'png' });

console.log(hash, url)
}
} satisfies Command;
export default class MyClient extends Client {
// ... class members

public constructor(options: ClientOptions) {
super(options);

this.database = new Database();
this.logger = new Logger();
this.config = config;
}

public async start(): Promise<void> {
await this.loadEvents();
await this.loadComponents();
await this.database.connect();
await this.login(this.config.token);
}

public async ready(): Promise<void> {
const mainGuild = this.guilds.cache.get(this.config.guildId);
await this.loadCommands({ refresh: false, guild: mainGuild });

this.setStatus();

this.developer = await this.users.fetch(this.config.developer).catch((error) => console.error(error?.stack ?? error))
this.logger.info(`Bot (${this.user.tag} [${this.user.id}]) is ready`);
}

private async loadEvents(): Promise<void> { ... }
private async loadComponents(): Promise<void> { ... }
private async loadCommands({ refresh = false, guild }: { refresh?: boolean, guild?: Guild } = {}): Promise<void> { ... }
private async registerSlashCommands(): Promise<void> { ... }
private async fetchCommands(): Promise<void> { ... }
private async setStatus(): Promise<void> { ... }
}
export default class MyClient extends Client {
// ... class members

public constructor(options: ClientOptions) {
super(options);

this.database = new Database();
this.logger = new Logger();
this.config = config;
}

public async start(): Promise<void> {
await this.loadEvents();
await this.loadComponents();
await this.database.connect();
await this.login(this.config.token);
}

public async ready(): Promise<void> {
const mainGuild = this.guilds.cache.get(this.config.guildId);
await this.loadCommands({ refresh: false, guild: mainGuild });

this.setStatus();

this.developer = await this.users.fetch(this.config.developer).catch((error) => console.error(error?.stack ?? error))
this.logger.info(`Bot (${this.user.tag} [${this.user.id}]) is ready`);
}

private async loadEvents(): Promise<void> { ... }
private async loadComponents(): Promise<void> { ... }
private async loadCommands({ refresh = false, guild }: { refresh?: boolean, guild?: Guild } = {}): Promise<void> { ... }
private async registerSlashCommands(): Promise<void> { ... }
private async fetchCommands(): Promise<void> { ... }
private async setStatus(): Promise<void> { ... }
}
i have collapsed all of the private methods
Frank
Frank2mo ago
Gist
.ts
GitHub Gist: instantly share code, notes, and snippets.
Frank
Frank2mo ago
originally i loaded commands in start method however when i parse refresh: false, it fetches the commands and client.application is undefined or null i cant remember which one i will try only load the test command
private async loadCommands({ refresh = false, guild }: { refresh?: boolean, guild?: Guild } = {}): Promise<void> {
const cmdFiles: string[] = await glob(new URL('../commands/**/*.js', import.meta.url).toString(), { absolute: true });

for (const file of cmdFiles) {
const props: Command = (await import('file://' + file))?.default;

if (props.data.name !== 'test') continue;

this.commands.set(props.data.name, props);
this.logger.info(`✅ ${props.data.name}`);
}

if (refresh) {
await this.registerSlashCommands(/* { guild } */);
} else {
await this.fetchCommands();
}
}
private async loadCommands({ refresh = false, guild }: { refresh?: boolean, guild?: Guild } = {}): Promise<void> {
const cmdFiles: string[] = await glob(new URL('../commands/**/*.js', import.meta.url).toString(), { absolute: true });

for (const file of cmdFiles) {
const props: Command = (await import('file://' + file))?.default;

if (props.data.name !== 'test') continue;

this.commands.set(props.data.name, props);
this.logger.info(`✅ ${props.data.name}`);
}

if (refresh) {
await this.registerSlashCommands(/* { guild } */);
} else {
await this.fetchCommands();
}
}
Console:
[2024-05-13 18:12:37] [INFO] > ✅ test
[2024-05-13 18:12:37] [INFO] > Bot (Rangers [...]) is ready
[2024-05-13 18:12:37] [INFO] > ✅ test
[2024-05-13 18:12:37] [INFO] > Bot (Rangers [...]) is ready
ran /test and banner: null i fetch commands in loadCommands so then if refresh is false it fetches otherwise if its true it gets the id's from the result of rest.put so i might just use rest to fetch the commands but then it would fetch everytime, i only need to fetch if i dont register (ik i can just comment the line out but thats just inconvenient imo) ill try logging banner with loading commands in start anyways to see if it makes a difference as this test command doesnt use the ids
public async start(): Promise<void> {
await this.loadEvents();
await this.loadComponents();
await this.loadCommands({ refresh: false/* , guild: mainGuild */ });
await this.database.connect();
await this.login(this.config.token);
}

public async ready(): Promise<void> {
const mainGuild = this.guilds.cache.get(this.config.guildId);
/* await this.loadCommands({ refresh: false, guild: mainGuild }); */

this.setStatus();

this.developer = await this.users.fetch(this.config.developer).catch((error) => console.error(error?.stack ?? error))
this.logger.info(`Bot (${this.user.tag} [${this.user.id}]) is ready`);
}
public async start(): Promise<void> {
await this.loadEvents();
await this.loadComponents();
await this.loadCommands({ refresh: false/* , guild: mainGuild */ });
await this.database.connect();
await this.login(this.config.token);
}

public async ready(): Promise<void> {
const mainGuild = this.guilds.cache.get(this.config.guildId);
/* await this.loadCommands({ refresh: false, guild: mainGuild }); */

this.setStatus();

this.developer = await this.users.fetch(this.config.developer).catch((error) => console.error(error?.stack ?? error))
this.logger.info(`Bot (${this.user.tag} [${this.user.id}]) is ready`);
}
in test command:
ClientUser {
id: '...',
bot: true,
system: false,
flags: UserFlagsBitField { bitfield: 0 },
username: 'Rangers',
globalName: null,
discriminator: '4537',
avatar: '...',
banner: null,
accentColor: null,
avatarDecoration: null,
verified: true,
mfaEnabled: true
}
ClientUser {
id: '...',
bot: true,
system: false,
flags: UserFlagsBitField { bitfield: 0 },
username: 'Rangers',
globalName: null,
discriminator: '4537',
avatar: '...',
banner: null,
accentColor: null,
avatarDecoration: null,
verified: true,
mfaEnabled: true
}
whilst only loading test.js too i will try console.log client user in ready it returns the banner, just command i will try logging it throughout interaction
client.on(Events.InteractionCreate, async interaction => {
console.log('interaction', interaction.client.user, 'interaction')
...
client.on(Events.InteractionCreate, async interaction => {
console.log('interaction', interaction.client.user, 'interaction')
...
interaction ClientUser {
id: '...',
bot: true,
system: false,
flags: UserFlagsBitField { bitfield: 0 },
username: 'Rangers',
globalName: null,
discriminator: '4537',
avatar: '...',
banner: null,
accentColor: null,
avatarDecoration: null,
verified: true,
mfaEnabled: true
} interaction
interaction ClientUser {
id: '...',
bot: true,
system: false,
flags: UserFlagsBitField { bitfield: 0 },
username: 'Rangers',
globalName: null,
discriminator: '4537',
avatar: '...',
banner: null,
accentColor: null,
avatarDecoration: null,
verified: true,
mfaEnabled: true
} interaction
yes, i change to true when i register my commands on startup or use /register however its 1 extra thing, possible but inconvenient however it didnt work when i tried moving registering to start
Frank
Frank2mo ago
client.once(Events.ClientReady, async me => {
const data = await me.users.fetch(me.user.id, { force: true, cache: true });
if (data) {
// testing purposes
Object.defineProperty(me, 'user', { writable: true, value: data });
}
// await me.user.fetch(true);

console.log('ready')

await client.ready();
});
client.once(Events.ClientReady, async me => {
const data = await me.users.fetch(me.user.id, { force: true, cache: true });
if (data) {
// testing purposes
Object.defineProperty(me, 'user', { writable: true, value: data });
}
// await me.user.fetch(true);

console.log('ready')

await client.ready();
});
i also tried seeing if i can forcefully overwrite it using object define property but banner is still null in the command
No description
Frank
Frank2mo ago
was only testing purposes
ClientUser {
id: '...',
bot: true,
system: false,
flags: UserFlagsBitField { bitfield: 0 },
username: 'Rangers',
globalName: null,
discriminator: '4537',
avatar: '...',
banner: '...', // not null
accentColor: null,
avatarDecoration: null,
verified: true,
mfaEnabled: true
}
ClientUser {
id: '...',
bot: true,
system: false,
flags: UserFlagsBitField { bitfield: 0 },
username: 'Rangers',
globalName: null,
discriminator: '4537',
avatar: '...',
banner: '...', // not null
accentColor: null,
avatarDecoration: null,
verified: true,
mfaEnabled: true
}
maybe when i run the command the cache gets updated internally and as it doesn't have the banner from the discord api it updates banner to null? idk when djs internally updates clientuser cache right now it only logs banner, but it will be to get the bots info would djs receive data for the client user on the interaction cmd? i dont do anything to the client in interaction create but i have the console log at the very top anyways so wouldnt matter what happens in it i only listen to it once too yes i will do that now, however i did try to fetch the client after i ran the ready functio
end of ready() 1a0ee5fc4061e55838f03c5eae82167e
end of ready event 1a0ee5fc4061e55838f03c5eae82167e
end of ready() 1a0ee5fc4061e55838f03c5eae82167e
end of ready event 1a0ee5fc4061e55838f03c5eae82167e
const client = new MyClient({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildPresences,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.MessageContent
],
sweepers: {
...Options.DefaultSweeperSettings,
users: {
interval: 3_600,
// eslint-disable-next-line unicorn/consistent-function-scoping
filter: () => user => user.bot && user.id !== user.client.user.id
},
guildMembers: {
interval: 3_600,
// eslint-disable-next-line unicorn/consistent-function-scoping
filter: () => member => member.user.bot && member.user.id !== member.client.user.id
}
}
})
const client = new MyClient({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildPresences,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.MessageContent
],
sweepers: {
...Options.DefaultSweeperSettings,
users: {
interval: 3_600,
// eslint-disable-next-line unicorn/consistent-function-scoping
filter: () => user => user.bot && user.id !== user.client.user.id
},
guildMembers: {
interval: 3_600,
// eslint-disable-next-line unicorn/consistent-function-scoping
filter: () => member => member.user.bot && member.user.id !== member.client.user.id
}
}
})
is it a discord api bug that banner isn't sent initially or is it intended i will try disable guild presences, i think i deleted the feature which used it banner is still null without guild presences i will try that however something i just noticed something strange the second i saw "✅ test" i ran the command, banner wasnt null. waited a few seconds, ran the command again and banner was null
Frank
Frank2mo ago
No description
Frank
Frank2mo ago
above message delete 19:09:12 out of those events, i only listen to ready, message delete, message create and interaction create yes i can add them back
Frank
Frank2mo ago
No description
Frank
Frank2mo ago
ill add the log to start and end of message delete and create im not sure, only thing which bulk deletes messages are my leaderboards but i removed the code and my message delete event returns if the channel name doesnt start with a specific word
Frank
Frank2mo ago
No description
Frank
Frank2mo ago
the message events took a while to come through this time "a while", few seconds longer than before i commented out my entire message delete and create events and banner is still null so must be internally i will try now still null
Frank
Frank2mo ago
@Qjuh
No description
Frank
Frank2mo ago
im not sure why message create and delete are even emitting
Frank
Frank2mo ago
No description
Frank
Frank2mo ago
seems to be once message delete is emitted @Qjuh this is my leaderboard code which i run on a timeout in my main file
console.log(17, this.client.user?.banner);
await channel.bulkDelete(5).catch(() => null);
console.log(18, this.client.user?.banner);

17 1a0ee5fc4061e55838f03c5eae82167e
18 null
MESSAGE_DELETE
MESSAGE_CREATE
console.log(17, this.client.user?.banner);
await channel.bulkDelete(5).catch(() => null);
console.log(18, this.client.user?.banner);

17 1a0ee5fc4061e55838f03c5eae82167e
18 null
MESSAGE_DELETE
MESSAGE_CREATE
correct i originally had this timeout in my ready event but i removed it and dumped it in my client so i didnt forget it (i could've just commented in my ready but it was 'clogging' it up when looking at it and i forgot to comment it in my main file) when i comment it out, banner remains as it should be however i look to reimplement this feature as i was only temporarily removing it none i tried bulk deleting in ready and using eval to bulk delete whilst keeping the leaderboard commented out and i cant seem to replicate this... im not sure whats happening but its to do with my leaderboard class i think but if i dont fetch a message? it only goes null when i bulkDelete in my leaderboard class i tried bulk deleting with eval and in my ready function but didnt go null however, when i comment out the bulkDelete method in the leaderboard class, it doesn't go null :Thonk: changing to channel.lastMessage?.delete() works, however in some cases, there might - A. not be a last message at all - B. there may be more than just the last message (i want to clear the entire channel) update.. it isn't a solution because lastMessage is null (not cached) and i can not fetch it because of this issue 🤦‍♂️ ah, i thought it would fetch them but i wasn't sure would this be considered a 'bug'? sounds good, atleast we (you) have been able to find the issue because this has been driving me crazy all day for now, i think i should fetch the client after initially purging the leaderboard channels, then using lastMessage to delete the posts btw, is there anyway i can keep updated or is it a case of keeping my eye out in #discord.js (discord.js)?