Skip to content

Commit 644f1a9

Browse files
committed
fix: multiple shards breaking role update
1 parent 5ec4c39 commit 644f1a9

File tree

6 files changed

+90
-72
lines changed

6 files changed

+90
-72
lines changed

bot.js

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ const fs = require('fs')
33
const express = require('express')
44
const AntiSpam = require('./templates/antispam')
55
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages] })
6-
const { updateRoles } = require('./functions/roles')
6+
const { updateRoles, updateSubscribedGuilds } = require('./functions/roles')
7+
const { guildCount } = require('./functions/client')
78

89
client.antispam = new AntiSpam()
910

@@ -12,10 +13,21 @@ fs.readdirSync('./events').filter(file => file.endsWith('.js')).forEach(async (f
1213
client.on(event.name, (...args) => { event.execute(...args) })
1314
})
1415

15-
// Start the bot
16-
client.login(process.env.TOKEN)
16+
client.once('ready', async (client) => {
17+
if (client.shard.ids[0] !== client.shard.count - 1) return
18+
19+
/**
20+
* Initialize the automatic role assignment
21+
*/
22+
setInterval(() => {
23+
updateSubscribedGuilds(client)
24+
}, 1000 * 60 * 60)
25+
26+
/**
27+
* Update the guild count
28+
*/
29+
guildCount(client)
1730

18-
try {
1931
// start the API
2032
const app = express()
2133
const PORT = process.env.EXPRESS_PORT || 3001
@@ -36,6 +48,17 @@ try {
3648
app.listen(PORT, () => {
3749
console.log(`🐉 API server is running on port ${PORT}`)
3850
})
39-
} catch (error) {
40-
console.error('Failed to start the API server:', error)
41-
}
51+
})
52+
53+
// Start the bot
54+
client.login(process.env.TOKEN)
55+
56+
const shutdown = async () => {
57+
console.log(`Shard ${client.shard?.ids[0] ?? '0'} is shutting down...`)
58+
await client.destroy()
59+
process.exit(0)
60+
}
61+
62+
process.on('SIGINT', shutdown)
63+
process.on('SIGTERM', shutdown)
64+
process.on('SIGUSR2', shutdown)

commands/roles.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ const setupRoles = async (interaction) => {
4141
.setAuthor({ name: name, iconURL: 'attachment://logo.png' })
4242
.setFooter({ text: `${name} ${getTranslation('strings.info', interaction.locale)}` })
4343

44-
4544
rolesFields.reverse().forEach((v, i) => card.addFields({ name: `Level ${10 - i}`, value: v, inline: true }))
4645

4746
if (error === 0) {

events/ready.js

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
const Discord = require('discord.js')
22
const mongo = require('../database/mongo')
33
const fs = require('fs')
4-
const { guildCount } = require('../functions/client')
54
const { REST } = require('@discordjs/rest')
65
const { Routes } = require('discord-api-types/v9')
7-
const { updateSubscribedGuilds } = require('../functions/roles')
86

97
module.exports = {
108
name: 'ready',
@@ -70,18 +68,6 @@ module.exports = {
7068
})
7169
})
7270

73-
/**
74-
* Initialize the automatic role assignment
75-
*/
76-
setInterval(() => {
77-
updateSubscribedGuilds(client)
78-
}, 1000 * 60 * 60)
79-
80-
/**
81-
* Update the guild count
82-
*/
83-
guildCount(client)
84-
8571
/**
8672
* Setup / commands
8773
*/

functions/client.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ const { ActivityType } = require('discord.js')
44
const guildCount = async (client) => {
55
const guildsSize = await client.shard.fetchClientValues('guilds.cache.size')
66
.then(results => results.reduce((acc, guildCount) => acc + guildCount, 0))
7-
client.user.setActivity(`/help | ${guildsSize} Guilds`, { type: ActivityType.Custom })
7+
8+
await client.shard.broadcastEval((c, guildsSize) => {
9+
c.user.setActivity(`/help | ${guildsSize} Guilds`, { type: 4 })
10+
}, { context: guildsSize })
811

912
// Send datas to top.gg
1013
if (process.env.TOPGG_TOKEN) {

functions/roles.js

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const REMOVE = 'REMOVE', ADD = 'ADD'
1010
const getRoleIds = (guildRoles) => Object.keys(Object.entries(guildRoles)[2][1])
1111
.filter(e => e.startsWith('level')).map(e => guildRoles[e])
1212

13-
const getCustomRoles = async (guildId) => {
13+
const getRoles = async (guildId) => {
1414
const roles = await GuildRoles.getRolesOf(guildId)
1515
if (!roles) return
1616

@@ -26,83 +26,90 @@ const getCustomRoles = async (guildId) => {
2626
}
2727

2828
const setupRoles = async (client, user, guildId, remove) => {
29-
const guildDatas = await client.guilds.fetch(guildId).catch(() => null)
30-
if (!guildDatas) return
29+
const guild = await client.guilds.fetch(guildId).catch(() => null)
30+
if (!guild) return console.warn(`Guild with ID ${guildId} not found`)
3131

32-
let members
32+
user = [user].flat().filter(e => e).filter(e => e.guildId === guild.id || !e.guildId)?.at(0)
3333

34-
if (user && user.length > 0) members = [await guildDatas.members.fetch({ user: user.at(0).discordId, cache: false }).catch(() => null)]
35-
else members = await guildDatas.members.fetch({ cache: false })
34+
console.info(`Setting up roles for guild: ${guild.name} (${guild.id})`)
3635

37-
const isPremium = await currentGuildIsPremium(client, guildId)
38-
const roles = isPremium ? await GuildCustomRole.getRolesOf(guildDatas.id) : await getCustomRoles(guildDatas.id)
39-
if (!roles) return
36+
const isPremium = await currentGuildIsPremium(guildId)
37+
const roles = isPremium ? await GuildCustomRole.getRolesOf(guild.id) : await getRoles(guild.id)
38+
if (!roles?.length) return console.warn(`No roles found for guild: ${guild.name} (${guild.id})`)
4039

41-
members?.forEach(async (member) => {
42-
if (!member?.user) return
43-
let user = await User.get(member.user.id)
40+
console.info(`Found ${roles.length} roles for guild: ${guild.name} (${guild.id})`)
4441

45-
if (!(user.length > 0)) return
46-
else if (user.length > 1) user = user.filter(e => e.guildId === guildDatas.id)
42+
const members = await client.shard.broadcastEval(async (c, { guildId, user }) => {
43+
const g = await c.guilds.fetch(guildId).catch(() => null)
44+
if (!g || c.shard.ids[0] !== g.shardId) return []
4745

48-
user = user.flat().at(0)
49-
50-
if (!user || !user.faceitId) return
51-
52-
const playerParam = { param: user.faceitId, faceitId: true }
53-
const stats = await getStats({ playerParam, game: defaultGame, matchNumber: 1 }).catch(() => null)
46+
const members = await g.members.fetch({ cache: false }).catch(() => [])
47+
return members.filter(m => m.user && !m.user.bot).map(m => ({
48+
id: m.id,
49+
user: { id: m.user.id, username: m.user.username, tag: m.user.tag },
50+
roles: m.roles.cache.map(r => r.id),
51+
nickname: m.nickname,
52+
})).filter(m => user ? m.user.id === user.discordId : true)
53+
}, { context: { guildId, user } }).then(res => res.flat())
5454

55-
if (!stats) return
55+
console.info(`Found ${members.length} members in guild: ${guild.name} (${guild.id})`)
5656

57-
const playerDatas = stats.playerDatas
57+
for (const member of members) {
58+
let user = await User.get(member.user.id)
59+
if (!(user.length > 0)) continue
60+
61+
if (user.length > 1) user = user.filter(e => e.guildId === guild.id)
62+
user = user.flat().at(0)
63+
if (!user?.faceitId) continue
5864

59-
if (!playerDatas?.games[defaultGame]) return
65+
console.info(`Processing user: ${member.user.username} (${member.user.id}) in guild: ${guild.name} (${guild.id})`)
6066

61-
const playerElo = playerDatas.games[defaultGame].faceit_elo
67+
const playerParam = { param: user.faceitId, faceitId: true }
68+
const stats = await getStats({ playerParam, game: defaultGame, matchNumber: 1 }).catch(() => null)
69+
if (!stats?.playerDatas?.games?.[defaultGame]) continue
70+
const playerElo = stats.playerDatas.games[defaultGame].faceit_elo
6271

63-
if (user.nickname) await member.edit({ nick: playerDatas.nickname }).catch(() => null)
72+
if (user.nickname) {
73+
await guild.members.fetch(member.id).then(m => m.setNickname(stats.playerDatas.nickname)
74+
.catch((error) => console.error(`Failed to set nickname for ${member.user.tag} in guild ${guild.name}:`, error)))
75+
}
6476

65-
roles.forEach(async (role) => {
77+
for (const role of roles) {
6678
const removeRole = playerElo < role.eloMin || playerElo > role.eloMax
67-
const roleId = role.roleId
68-
const rolesFit = !!member.roles.resolve(roleId)
69-
70-
// Remove role if it doesn't fit the criteria and the role is assigned or if the remove flag is set
71-
if ((removeRole && rolesFit) || remove)
72-
await member.roles.remove(roleId)
73-
.then(e => logRoleUpdate(client, member, role, guildDatas, playerElo, REMOVE))
74-
.catch((err) => handleRoleErrors(err, role))
75-
76-
// Add role if it fits the criteria and the role isn't already assigned and the remove flag isn't set
77-
if ((!removeRole && !rolesFit) && !remove)
78-
await member.roles.add(roleId)
79-
.then(e => logRoleUpdate(client, member, role, guildDatas, playerElo, ADD))
80-
.catch((err) => handleRoleErrors(err, role))
81-
})
82-
})
79+
const hasRole = member.roles.includes(role.roleId)
80+
81+
if ((removeRole && hasRole) || remove) {
82+
await guild.members.fetch(member.id).then(m => m.roles.remove(role.roleId).catch(() => null))
83+
logRoleUpdate(client, member, role, guild, playerElo, REMOVE)
84+
} else if (!removeRole && !hasRole && !remove) {
85+
await guild.members.fetch(member.id).then(m => m.roles.add(role.roleId).catch(() => null))
86+
logRoleUpdate(client, member, role, guild, playerElo, ADD)
87+
}
88+
}
89+
}
8390
}
8491

8592
const updateRoles = async (client, discordId, guildId, remove = false) => {
8693
let user, guilds
8794

8895
if (discordId) {
8996
user = [await User.get(discordId)].flat()
90-
if (user.length > 1) user = user.filter(e => e.guildId === guildId)
91-
guildId = user.at(0)?.guildId
97+
if (user.length > 1) {
98+
user = user.filter(e => guildId ? [guildId].flat().includes(e.guildId) : true)
99+
}
100+
guildId = user.map(e => e.guildId).filter((e, i, a) => a.indexOf(e) === i)
92101
}
93102

94103
if (guildId) guilds = [guildId].flat()
95104
else guilds = (await GuildCustomRole.getAll()).map(e => e.guildId).filter((e, i, a) => a.indexOf(e) === i)
96105

106+
console.info(`Updating roles for ${guilds.length} guilds, user: ${discordId}, remove: ${remove}`)
107+
97108
Promise.all(guilds.map(async guild => await setupRoles(client, user, guild, remove).catch(console.error)))
98109
.then(() => { if (remove) User.remove(discordId, guildId) })
99110
.catch(console.error)
100111
}
101112

102-
const handleRoleErrors = (err, role) => {
103-
if (err.status === '404') GuildCustomRole.remove(role.guildId, role.roleId)
104-
}
105-
106113
const logRoleUpdate = (client, member, role, guildDatas, playerElo, action) => {
107114
client.guilds.fetch(logGuild)
108115
.then(guild => guild.channels.fetch(logChannel))

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"test": "echo \"Error: no test specified\" && exit 1",
88
"start": "node index.js",
9-
"dev": "nodemon --trace-warnings bot.js",
9+
"dev": "nodemon --trace-warnings index.js",
1010
"register-roles": "node ./scripts/register_roles.js",
1111
"check-guild": "node ./scripts/check_guild.js",
1212
"lint": "eslint --ext .js .",

0 commit comments

Comments
 (0)