Search code examples
discorddiscord.js

Is there a way to access the children of a CategoryChannel before it is deleted? [Discord.js]


I handle a channelDelete event in my discord bot. My original intent was to do the following:

  1. Listen for when a channel is deleted
  2. Check to see if its type equals 'GUILD_CATEGORY'
  3. Delete all the channels under that category

I can typically access channels under a CategoryChannel through its property called children anywhere else except during this event...

module.exports = {
    name: 'channelDelete',
    once: false,
    async execute(channel) {
        if(channel.type == 'GUILD_CATEGORY')
            for(let [child_id, child] of channel.children)
                if (child.deletable) child.delete();
    },
}

I can confirm the code is being executed, but the problem is that the incoming channel object is in a state where it is already deleted, and I cannot get the children:

  • During debugging, I noticed the channel object has the property deleted: true
  • The children property is empty, even though I know there were channels in that channel category prior to deletion

Is there a way for my bot to collect and handle the children of a CategoryChannel prior to its deletion?


Solution

  • Why?

    Unfortunately, this is how CategoryChannels work in discord.js...
    When the category is deleted, discord.js sends a request to the API to delete the channel. Only then, Discord sends you the event after the category is deleted.
    What happens next is that the children are not located in the category anymore! So you will not be able to get the children inside the CategoryChannel object.


    This is the code for the children property

    get children() {
        return this.guild.channels.cache.filter(c => c.parentId === this.id);
    }
    

    It filters the channels which are in the guild, but those children channels are not located in the category anymore. Which is the reason the property is empty.

    The (Only?) Solution

    This will require caching the channels themselves. As there aren't really any other solutions.
    You may cache the channels in different ways.
    Just remember... there are instances and references in Javascript, and failure to acknowledge may lead to weird behaviors. The following code only works for small bots without sharding, just so you know.

    const cachedChannels = new Map()
    
    client.on('ready', () => {
      // Looping thru guilds...
      client.guilds.forEach((guild) => {
        // Filtering the channels to category channels only and looping thru them...
        guild.channels.filter((channel) => channel.type === "GUILD_CATEGORY").forEach((category) => {
          // Note on references and instances: Numbers, Strings and Booleans do not have instances like Object, Arrays, etc. do
          // We are using an array here to abuse the references
          cachedChannels.set(category.id, [])
    
          // Looping thru the children channels...
          category.children.forEach((children) => {
            // This is the array we've created earlier
            const storedChannels = cachedChannels.get(category.id)
    
            // Pushing the ID so we can fetch them later
            storedChannels.push(children.id)
          })
        })
      })
    })
    
    client.on('channelDelete', (channel) => {
      if (channel.type === "GUILD_CATEGORY") {
        // Looping thru our cached channel IDs defined earlier
        cachedChannels.get(channel.id).forEach((id) => {
          const child = channel.guild.channels.get(id)
          child.delete()
        })
    
        // Delete the cached IDs to save memory
        cachedChannels.delete(channel.id)
      }
    })