Finally getting around to updating my bot from v12 to v13 and having an issue that I can't seem to solve. I have an invite tracker that works perfectly on v12 but will not function on v13. I have included the code below, the commented lines are the v12 code that I know needed changing for v13.
// const Discord = require('discord.js')
const {
Client,
Intents
} = require('discord.js')
// const client = new Discord.Client({
// partials: ['MESSAGE', 'CHANNEL', 'REACTION']
// })
const client = new Client({
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.GUILD_BANS, Intents.FLAGS.GUILD_EMOJIS_AND_STICKERS, Intents.FLAGS.GUILD_INTEGRATIONS, Intents.FLAGS.GUILD_WEBHOOKS, Intents.FLAGS.GUILD_INVITES, Intents.FLAGS.GUILD_VOICE_STATES, Intents.FLAGS.GUILD_PRESENCES, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MESSAGE_REACTIONS, Intents.FLAGS.GUILD_MESSAGE_TYPING, Intents.FLAGS.DIRECT_MESSAGES, Intents.FLAGS.DIRECT_MESSAGE_REACTIONS, Intents.FLAGS.DIRECT_MESSAGE_TYPING],
partials: ['MESSAGE', 'CHANNEL', 'REACTION']
})
const guildInvites = new Map()
// client.on('inviteCreate', async invite => guildInvites.set(invite.guild.id, await invite.guild.fetchInvites()))
client.on('inviteCreate', async invite => guildInvites.set(invite.guild.id, await invite.guild.invites.fetch()))
client.once('ready', () => {
client.guilds.cache.forEach(guild => {
//guild.fetchInvites()
guild.invites.fetch()
.then(invites => guildInvites.set(guild.id, invites))
.catch(err => {
console.log(err)
console.log(guildInvites)
client.channels.cache.get('channelId').send({
content: `${err}`
})
})
})
})
client.on('guildMemberAdd', async member => {
const cachedInvites = guildInvites.get(member.guild.id)
// const newInvites = await member.guild.fetchInvites()
const newInvites = await member.guild.invites.fetch()
guildInvites.set(member.guild.id, newInvites)
try {
const usedInvite = newInvites.find(inv => cachedInvites.get(inv.code).uses < inv.uses)
console.log(cachedInvites)
console.log(newInvites)
console.log(usedInvite)
console.log(`The code ${usedInvite.code} was just used by ${member.user.username}.`)
} catch (err) {
console.log(err)
client.channels.cache.get('channelId').send({
content: `${err}`
})
}
})
This is the console log result:
console.log(cachedInvites) => returns below
Collection(2) [Map] {
'11111111' => Invite {
guild: Guild {
id: 'xxxx',
name: "Testing Server",
icon: null,
features: [Array],
commands: [GuildApplicationCommandManager],
members: [GuildMemberManager],
channels: [GuildChannelManager],
bans: [GuildBanManager],
roles: [RoleManager],
presences: PresenceManager {},
voiceStates: [VoiceStateManager],
stageInstances: [StageInstanceManager],
invites: [GuildInviteManager],
deleted: false,
available: true,
shardId: 0,
splash: null,
banner: null,
description: null,
verificationLevel: 'LOW',
vanityURLCode: null,
nsfwLevel: 'DEFAULT',
discoverySplash: null,
memberCount: 3,
large: false,
applicationId: null,
afkTimeout: 900,
afkChannelId: 'xxxx',
systemChannelId: 'xxxx',
premiumTier: 'NONE',
premiumSubscriptionCount: 0,
explicitContentFilter: 'ALL_MEMBERS',
mfaLevel: 'NONE',
joinedTimestamp: 1633812225872,
defaultMessageNotifications: 'ONLY_MENTIONS',
systemChannelFlags: [SystemChannelFlags],
maximumMembers: 250000,
maximumPresences: null,
approximateMemberCount: null,
approximatePresenceCount: null,
vanityURLUses: null,
rulesChannelId: 'xxxx',
publicUpdatesChannelId: 'xxxx',
preferredLocale: 'en-US',
ownerId: 'xxxx',
emojis: [GuildEmojiManager],
stickers: [GuildStickerManager]
},
code: '11111111',
presenceCount: null,
memberCount: null,
temporary: false,
maxAge: 604800,
uses: 40,
maxUses: 0,
inviter: User {
id: 'xxxx',
bot: false,
system: false,
flags: [UserFlags],
username: 'xxxx',
discriminator: 'xxxx',
avatar: 'xxxx',
banner: undefined,
accentColor: undefined
},
targetUser: null,
targetApplication: null,
targetType: null,
channel: TextChannel {
type: 'GUILD_TEXT',
deleted: false,
guild: [Guild],
guildId: 'xxxx',
parentId: 'xxxx',
permissionOverwrites: [PermissionOverwriteManager],
messages: [MessageManager],
threads: [ThreadManager],
nsfw: false,
id: 'xxxx',
name: 'welcome',
rawPosition: 3,
topic: null,
lastMessageId: 'xxxx',
rateLimitPerUser: 0
},
createdTimestamp: 1633922331801,
_expiresTimestamp: null,
stageInstance: null
},
'22222222' => Invite {
guild: Guild {
id: 'xxxx',
name: "Testing Server",
icon: null,
features: [Array],
commands: [GuildApplicationCommandManager],
members: [GuildMemberManager],
channels: [GuildChannelManager],
bans: [GuildBanManager],
roles: [RoleManager],
presences: PresenceManager {},
voiceStates: [VoiceStateManager],
stageInstances: [StageInstanceManager],
invites: [GuildInviteManager],
deleted: false,
available: true,
shardId: 0,
splash: null,
banner: null,
description: null,
verificationLevel: 'LOW',
vanityURLCode: null,
nsfwLevel: 'DEFAULT',
discoverySplash: null,
memberCount: 3,
large: false,
applicationId: null,
afkTimeout: 900,
afkChannelId: 'xxxx',
systemChannelId: 'xxxx',
premiumTier: 'NONE',
premiumSubscriptionCount: 0,
explicitContentFilter: 'ALL_MEMBERS',
mfaLevel: 'NONE',
joinedTimestamp: 1633812225872,
defaultMessageNotifications: 'ONLY_MENTIONS',
systemChannelFlags: [SystemChannelFlags],
maximumMembers: 250000,
maximumPresences: null,
approximateMemberCount: null,
approximatePresenceCount: null,
vanityURLUses: null,
rulesChannelId: 'xxxx',
publicUpdatesChannelId: 'xxxx',
preferredLocale: 'en-US',
ownerId: 'xxxx',
emojis: [GuildEmojiManager],
stickers: [GuildStickerManager]
},
code: '22222222',
presenceCount: null,
memberCount: null,
temporary: false,
maxAge: 604800,
uses: 21,
maxUses: 0,
inviter: User {
id: 'xxxx',
bot: false,
system: false,
flags: [UserFlags],
username: 'xxxx',
discriminator: 'xxxx',
avatar: 'xxx',
banner: undefined,
accentColor: undefined
},
targetUser: null,
targetApplication: null,
targetType: null,
channel: TextChannel {
type: 'GUILD_TEXT',
deleted: false,
guild: [Guild],
guildId: 'xxxxx',
parentId: 'xxxx',
permissionOverwrites: [PermissionOverwriteManager],
messages: [MessageManager],
threads: [ThreadManager],
nsfw: false,
id: 'xxxx',
name: 'guest-chat',
rawPosition: 48,
topic: 'Guest Invitation Link',
lastMessageId: null,
rateLimitPerUser: 0
},
createdTimestamp: 1633969178889,
_expiresTimestamp: null,
stageInstance: null
}
}
console.log(newInvites) => returns below
Collection(2) [Map] {
'11111111' => Invite {
guild: Guild {
id: 'xxxx',
name: "Testing Server",
icon: null,
features: [Array],
commands: [GuildApplicationCommandManager],
members: [GuildMemberManager],
channels: [GuildChannelManager],
bans: [GuildBanManager],
roles: [RoleManager],
presences: PresenceManager {},
voiceStates: [VoiceStateManager],
stageInstances: [StageInstanceManager],
invites: [GuildInviteManager],
deleted: false,
available: true,
shardId: 0,
splash: null,
banner: null,
description: null,
verificationLevel: 'LOW',
vanityURLCode: null,
nsfwLevel: 'DEFAULT',
discoverySplash: null,
memberCount: 3,
large: false,
applicationId: null,
afkTimeout: 900,
afkChannelId: 'xxxx',
systemChannelId: 'xxxx',
premiumTier: 'NONE',
premiumSubscriptionCount: 0,
explicitContentFilter: 'ALL_MEMBERS',
mfaLevel: 'NONE',
joinedTimestamp: 1633812225872,
defaultMessageNotifications: 'ONLY_MENTIONS',
systemChannelFlags: [SystemChannelFlags],
maximumMembers: 250000,
maximumPresences: null,
approximateMemberCount: null,
approximatePresenceCount: null,
vanityURLUses: null,
rulesChannelId: 'xxxx',
publicUpdatesChannelId: 'xxxx',
preferredLocale: 'en-US',
ownerId: 'xxxx',
emojis: [GuildEmojiManager],
stickers: [GuildStickerManager]
},
code: '11111111',
presenceCount: null,
memberCount: null,
temporary: false,
maxAge: 604800,
uses: 40,
maxUses: 0,
inviter: User {
id: 'xxxx',
bot: false,
system: false,
flags: [UserFlags],
username: 'xxxx',
discriminator: 'xxxx',
avatar: 'xxxx',
banner: undefined,
accentColor: undefined
},
targetUser: null,
targetApplication: null,
targetType: null,
channel: TextChannel {
type: 'GUILD_TEXT',
deleted: false,
guild: [Guild],
guildId: 'xxxx',
parentId: 'xxxx',
permissionOverwrites: [PermissionOverwriteManager],
messages: [MessageManager],
threads: [ThreadManager],
nsfw: false,
id: 'xxxx',
name: 'welcome',
rawPosition: 3,
topic: null,
lastMessageId: 'xxxx',
rateLimitPerUser: 0
},
createdTimestamp: 1633922331801,
_expiresTimestamp: null,
stageInstance: null
},
'22222222' => Invite {
guild: Guild {
id: '878285237082271744',
name: "Testing Server",
icon: null,
features: [Array],
commands: [GuildApplicationCommandManager],
members: [GuildMemberManager],
channels: [GuildChannelManager],
bans: [GuildBanManager],
roles: [RoleManager],
presences: PresenceManager {},
voiceStates: [VoiceStateManager],
stageInstances: [StageInstanceManager],
invites: [GuildInviteManager],
deleted: false,
available: true,
shardId: 0,
splash: null,
banner: null,
description: null,
verificationLevel: 'LOW',
vanityURLCode: null,
nsfwLevel: 'DEFAULT',
discoverySplash: null,
memberCount: 3,
large: false,
applicationId: null,
afkTimeout: 900,
afkChannelId: 'xxxx',
systemChannelId: 'xxxx',
premiumTier: 'NONE',
premiumSubscriptionCount: 0,
explicitContentFilter: 'ALL_MEMBERS',
mfaLevel: 'NONE',
joinedTimestamp: 1633812225872,
defaultMessageNotifications: 'ONLY_MENTIONS',
systemChannelFlags: [SystemChannelFlags],
maximumMembers: 250000,
maximumPresences: null,
approximateMemberCount: null,
approximatePresenceCount: null,
vanityURLUses: null,
rulesChannelId: 'xxxx',
publicUpdatesChannelId: 'xxxx',
preferredLocale: 'en-US',
ownerId: 'xxxx',
emojis: [GuildEmojiManager],
stickers: [GuildStickerManager]
},
code: '22222222',
presenceCount: null,
memberCount: null,
temporary: false,
maxAge: 604800,
uses: 22,
maxUses: 0,
inviter: User {
id: 'xxxx',
bot: false,
system: false,
flags: [UserFlags],
username: 'xxxx',
discriminator: 'xxxx',
avatar: 'xxxx',
banner: undefined,
accentColor: undefined
},
targetUser: null,
targetApplication: null,
targetType: null,
channel: TextChannel {
type: 'GUILD_TEXT',
deleted: false,
guild: [Guild],
guildId: 'xxxx',
parentId: 'xxxx',
permissionOverwrites: [PermissionOverwriteManager],
messages: [MessageManager],
threads: [ThreadManager],
nsfw: false,
id: 'xxxx',
name: 'guest-chat',
rawPosition: 48,
topic: 'Guest Invitation Link',
lastMessageId: null,
rateLimitPerUser: 0
},
createdTimestamp: 1633969178889,
_expiresTimestamp: null,
stageInstance: null
}
}
console.log(usedInvite) => returns undefined
The error message I get is obviously:
TypeError: Cannot read property 'code' of undefined
The issue you are having most likely stems from the fact that you are saving the entire Invites Collection object in your guildInvites Map. Remember that in Javascript, when you save a single object in multiple different locations, each location still points to the same object. Basically, when you save the invites object or invite.guild.invites.fetch() to your guildInvites Map, it refers to the exact same object as newInvites will. In other words, cachedInvites and newInvites are both referring to the same object; because of this, they will contain the exact same values. Whenever an invite in newInvites is updated, it will automatically also update the invite in cachedInvites. This may be happening only now due to some changes in discord.js' code for invite management in v13.
I have not, however, looked at the discord.js source code for the new InviteManager, so it is possible that some other, similar issue is at play here. However, the solution in this answer works regardless.
Because the cache and new invite lists were both the same, the uses between the cached and used invite ended up being equal (causing the cachedInvites.get(inv.code).uses < inv.uses condition to not be met). Thus, newInvites.find() could not find the invite you were looking for, and usedInvite ended up being undefined.
There are several ways you could fix this issue. My preference is to not save the entire invites collection to your invite cache. Since you only need to know the cached invite's code and old number of uses, you only need to save that information. No need to save entire Invite objects to your cache when you only need two small pieces of information from each cached invite.
This is my proposed new structure of your cachedInvites. As you can see, it is much simpler and more efficient than before. It also solves the aforementioned possible JS issues with a single object being saved to multiple locations, by not directly saving the invites object in your cache.
Collection(2) [Map] {
'11111111' => 40,
'22222222' => 21
}
And here is my full solution, using this new, simpler structure for cached invites. I've tested it, and it works:
client.on('inviteCreate', async invite => {
const invites = await invite.guild.invites.fetch();
const codeUses = new Map();
invites.each(inv => codeUses.set(inv.code, inv.uses));
guildInvites.set(invite.guild.id, codeUses);
})
client.once('ready', () => {
client.guilds.cache.forEach(guild => {
guild.invites.fetch()
.then(invites => {
console.log("INVITES CACHED");
const codeUses = new Map();
invites.each(inv => codeUses.set(inv.code, inv.uses));
guildInvites.set(guild.id, codeUses);
})
.catch(err => {
console.log("OnReady Error:", err)
})
})
})
client.on('guildMemberAdd', async member => {
const cachedInvites = guildInvites.get(member.guild.id)
const newInvites = await member.guild.invites.fetch();
try {
const usedInvite = newInvites.find(inv => cachedInvites.get(inv.code) < inv.uses);
console.log("Cached", [...cachedInvites.keys()])
console.log("New", [...newInvites.values()].map(inv => inv.code))
console.log("Used", usedInvite)
console.log(`The code ${usedInvite.code} was just used by ${member.user.username}.`)
} catch (err) {
console.log("OnGuildMemberAdd Error:", err)
}
newInvites.each(inv => cachedInvites.set(inv.code, inv.uses));
guildInvites.set(member.guild.id, cachedInvites);
});