From b685ec331e076d1549356d545a61703838f520c2 Mon Sep 17 00:00:00 2001 From: Jesfery Date: Sun, 28 Nov 2021 19:26:26 +1100 Subject: [PATCH 1/3] Rate limit workaround for channel renaming --- listeners/channelState.js | 168 ++++++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 62 deletions(-) diff --git a/listeners/channelState.js b/listeners/channelState.js index 526fe87..9f5af76 100644 --- a/listeners/channelState.js +++ b/listeners/channelState.js @@ -1,4 +1,5 @@ const utils = require('../utils.js'); +const Discord = require('discord.js'); /** * Checks to see if the voice channel is a child of a category we can manage @@ -25,66 +26,42 @@ function canActOn(channel) { * * @param {CategoryChannel} category The category */ -function manageChannels(category) { - let guild = category.guild, - voiceChannels, - emptyVoiceChannels, - promises, - createNewChannel = false; - - voiceChannels = category.children.filter(channel => { - return channel.type === 'voice'; - }); +function manageChannels(cat) { - emptyVoiceChannels = voiceChannels.filter(channel => { - return channel.members.size === 0; - }); + cat.fetch() + .then((category) => { + let guild = category.guild; - //Ensure one empty channel - promises = []; - if (emptyVoiceChannels.size > 0) { - emptyVoiceChannels.forEach(channel => { - let permissionOverwrites = category.permissionOverwrites.map(() => {}); - if (channel.id === emptyVoiceChannels.first().id) { - //Edit happens in lockPermissions. Set userLimit on channel data. - /*promises.push(channel.edit({ - userLimit: 0 - }));*/ - channel.userLimit = 0; - promises.push(channel.lockPermissions()); - } else { - promises.push(channel.delete()); - voiceChannels.delete(channel.id); - } - }); - } else if (emptyVoiceChannels.size === 0) { - createNewChannel = true; - } + let voiceChannels = category.children.filter(channel => { + return channel.type === 'voice'; + }); - Promise.all(promises).then(() => { - let index = 1, - channelName; + let index = 1, + channelName; - voiceChannels = category.children.filter(channel => { - return channel.type === 'voice'; - }); + let populatedChannels = voiceChannels.filter(channel => { + return channel.members.size > 0; + }); - voiceChannels.forEach(channel => { - channelName = getChannelName(channel, index); - if (channelName !== channel.name) { - channel.setName(channelName); - } - index++; - }); + populatedChannels.forEach(channel => { + channelName = getChannelName(channel, index); + renameChannel(channel, channelName); + index++; + }); + + let emptyVoiceChannels = voiceChannels.filter(channel => { + return channel.members.size === 0; + }); + + emptyVoiceChannels.forEach(channel => { + channel.delete(); + }); - if (createNewChannel) { guild.channels.create('Voice #' + index, { type: 'voice', parent: category }); - } - }); - + }); } /** @@ -98,10 +75,11 @@ function getChannelName(channel, index) { max = 0, activityName, channelName; + channel.members.forEach(member => { let activities = utils.get(member, 'presence.activities'); - if(member.user.bot) { + if (member.user.bot) { return; } @@ -144,6 +122,74 @@ function getChannelName(channel, index) { return channelName; } +const renameCoolDowns = new Discord.Collection(); +const rateLimit = (1000 * 60 * 10) + 1000; + +/** + * An attempt to avoid rate limits. If it can delete and recreate the channel it will. Otherwise, + * it will queue the latest name until such time as it can rename it. + * @param {Channel} channel the channel to be renamed + * @param {String} name the new name of the channel + */ +function renameChannel(channel, name) { + if (channel.members.size === 0) { //Empty channel. Delete and re-create + let category = channel.parent; + channel.delete().then(() => { + guild.channels.create(name, { + type: 'voice', + parent: category + }); + }); + return; + } + + if (channel.name === name) { + return; + } + + let channelCoolDown; + let channelId = channel.id; + if (!renameCoolDowns.has(channelId)) { + channelCoolDown = new Discord.Collection(); + channelCoolDown.set('count', 0); + channelCoolDown.set('name', undefined); + //I'll put the timeout in the collection for now, but its not in use atm. + console.log(`Cooldown started for channel: ${channelId}(${channel.name})`); + channelCoolDown.set('timeout', setTimeout(() => { + let ccd = renameCoolDowns.get(channelId); + let queuedName = ccd.get('name'); + if (queuedName !== undefined) { + ccd.get('channel').fetch() + .then((queuedChannel) => { + console.log(`Completing rename of channel: ${channelId}(${queuedChannel.name}). New name should be ${queuedName}`); + queuedChannel.setName(queuedName).catch((e) => { + //Channel likely deleted before the timeout completed. Ignore. + }); + }) + .catch((e) => { + //Channel likely deleted before the timeout completed. Ignore. + }); + } + renameCoolDowns.delete(channelId); + }, rateLimit)); + renameCoolDowns.set(channelId, channelCoolDown); + } else { + channelCoolDown = renameCoolDowns.get(channelId); + } + let count = channelCoolDown.get('count'); + count++; + console.log(`${count} requests to rename channel: ${channelId}(${channel.name}). Requested name is '${name}'`); + channelCoolDown.set('count', count); + channelCoolDown.set('channel', channel); + + if (count < 3) { + channel.setName(name); + } else { + console.log(`Queueing name '${name}' for channel: ${channelId}(${channel.name})`); + channelCoolDown.set('name', name); + } +} + module.exports = { init: function (client) { @@ -166,19 +212,17 @@ module.exports = { client.on('voiceStateUpdate', (oldState, newState) => { let newUserChannel = newState.channel, oldUserChannel = oldState.channel, - oldCategoryID, - newCategory; + newCategoryID; - //If a user enters of leaves a configured category, update it. - if (oldUserChannel != null && canActOn(oldUserChannel) && (newUserChannel == null || !newUserChannel.equals(oldUserChannel))) { - oldCategoryID = oldUserChannel.parentID; - manageChannels(oldUserChannel.parent); + //If a user enters or leaves a configured category, update it. + if (newUserChannel != null && canActOn(newUserChannel) && (oldUserChannel == null || !newUserChannel.equals(oldUserChannel))) { + newCategoryID = newUserChannel.parentID; + manageChannels(newUserChannel.parent); } - - if (newUserChannel != null && canActOn(newUserChannel) && (oldUserChannel == null || !newUserChannel.equals(oldUserChannel))) { - if (oldCategoryID !== newUserChannel.parentID) { //No need to manage the same category twice. - manageChannels(newUserChannel.parent); + if (oldUserChannel != null && canActOn(oldUserChannel) && (newUserChannel == null || !newUserChannel.equals(oldUserChannel))) { + if (newCategoryID !== oldUserChannel.parentID) { //No need to manage the same category twice. + manageChannels(oldUserChannel.parent); } } }); @@ -188,7 +232,7 @@ module.exports = { let newUserChannel = utils.get(newPresence, 'member.voice.channel'); if (newUserChannel != null) { //Shouldnt be necessary to manage an entire category when the presence updates. - newUserChannel.setName(getChannelName(newUserChannel)); + renameChannel(newUserChannel, getChannelName(newUserChannel)); } } }); From 173a681868dd42a25fbf9bb318bcd7bc8eca26b1 Mon Sep 17 00:00:00 2001 From: Jesfery Date: Mon, 29 Nov 2021 23:18:23 +1100 Subject: [PATCH 2/3] Rate limit workaround for channel renaming Exception handler on normal name change. --- listeners/channelState.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/listeners/channelState.js b/listeners/channelState.js index 9f5af76..de9d927 100644 --- a/listeners/channelState.js +++ b/listeners/channelState.js @@ -183,7 +183,9 @@ function renameChannel(channel, name) { channelCoolDown.set('channel', channel); if (count < 3) { - channel.setName(name); + channel.setName(name).catch((e) => { + //Channel likely does not exist. Ignore. + }); } else { console.log(`Queueing name '${name}' for channel: ${channelId}(${channel.name})`); channelCoolDown.set('name', name); From 0e42446ad4a8c134474f8c0371bf32a69f660085 Mon Sep 17 00:00:00 2001 From: Jesfery Date: Fri, 26 Aug 2022 00:50:38 +1000 Subject: [PATCH 3/3] Revised channel rename timeout impl --- listeners/channelState.js | 40 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/listeners/channelState.js b/listeners/channelState.js index de9d927..b2723fe 100644 --- a/listeners/channelState.js +++ b/listeners/channelState.js @@ -153,25 +153,6 @@ function renameChannel(channel, name) { channelCoolDown = new Discord.Collection(); channelCoolDown.set('count', 0); channelCoolDown.set('name', undefined); - //I'll put the timeout in the collection for now, but its not in use atm. - console.log(`Cooldown started for channel: ${channelId}(${channel.name})`); - channelCoolDown.set('timeout', setTimeout(() => { - let ccd = renameCoolDowns.get(channelId); - let queuedName = ccd.get('name'); - if (queuedName !== undefined) { - ccd.get('channel').fetch() - .then((queuedChannel) => { - console.log(`Completing rename of channel: ${channelId}(${queuedChannel.name}). New name should be ${queuedName}`); - queuedChannel.setName(queuedName).catch((e) => { - //Channel likely deleted before the timeout completed. Ignore. - }); - }) - .catch((e) => { - //Channel likely deleted before the timeout completed. Ignore. - }); - } - renameCoolDowns.delete(channelId); - }, rateLimit)); renameCoolDowns.set(channelId, channelCoolDown); } else { channelCoolDown = renameCoolDowns.get(channelId); @@ -189,6 +170,27 @@ function renameChannel(channel, name) { } else { console.log(`Queueing name '${name}' for channel: ${channelId}(${channel.name})`); channelCoolDown.set('name', name); + + if (!channelCoolDown.get('timeout')) { + console.log(`Cooldown started for channel: ${channelId}(${channel.name})`); + channelCoolDown.set('timeout', setTimeout(() => { + let ccd = renameCoolDowns.get(channelId); + let queuedName = ccd.get('name'); + if (queuedName !== undefined) { + ccd.get('channel').fetch() + .then((queuedChannel) => { + console.log(`Completing rename of channel: ${channelId}(${queuedChannel.name}). New name should be ${queuedName}`); + queuedChannel.setName(queuedName).catch((e) => { + //Channel likely deleted before the timeout completed. Ignore. + }); + }) + .catch((e) => { + //Channel likely deleted before the timeout completed. Ignore. + }); + } + renameCoolDowns.delete(channelId); + }, rateLimit)); + } } }