commands/hook.js

/**
 * Lists all {@link Hook|Hooks} enabled in the context, toggles the Hook provided, or lists all Hooks that can be enabled
 *
 * @type {Command}
 * @memberof Command
 * @name hook
 * @static
 */
module.exports = {
  name: 'hook',
  ownerOnly: true,
  cooldown: 1,
  usage: '[<Hook name>]/[list]',
  aliases: ['hooks'],
  description: 'Lists all Hooks enabled here, toggles the Hook provided, or lists all Hooks that can be enabled.',
  async execute ({ args, server, message }) {
    const prefix = server.controllers.get('CommandController').determinePrefix(message)

    const HOOK_TOGGLE_TXT = `Use \`${prefix}hook <Hook name>\` to toggle a Hook.`
    const HOOK_TXT = `Use \`${prefix}hook\` to see all Hooks enabled here. ${HOOK_TOGGLE_TXT}`
    const HOOK_LIST_TXT = `Use \`${prefix}hook list\` to see the Hooks you can enable here. ${HOOK_TOGGLE_TXT}`

    // helper function that initializes the database document if missing
    const initializeIfMissing = async (hook, doc) => {
      if (doc.status === 404 || !doc.contexts) {
        return await server.controllers.get('DatabaseController').updateDoc({
          db: 'hook',
          id: hook.name,
          payload: {
            contexts: []
          }
        })
      } else {
        return doc
      }
    }

    // if no arguments provided, list all Hooks enabled for this context
    if (args.length === 0) {
      const globalEmbedFieldValues = []
      const guildEmbedFieldValues = []
      const channelEmbedFieldValues = []

      // get all loaded Hooks and their database documents
      for (const h of server.hooks.values()) {
        // fetch this Hook's database document
        let doc = await server.controllers.get('DatabaseController').fetchDoc({ db: 'hook', id: h.name })

        // if missing, initialize this Hook's database document
        doc = await initializeIfMissing(h, doc)

        const contexts = doc.contexts

        // get all contexts with this Hook enabled
        for (const c of contexts) {
          // keep only enabled contexts
          const contextDoc = await server.controllers.get('DatabaseController').fetchDoc({
            db: 'hook',
            id: `${h.name}_${c}`
          })
          if (!contextDoc.enabled) continue

          const args = (contextDoc.args && contextDoc.args.length) ? `- *argument${contextDoc.args.length > 1 ? 's' : ''}:* \`${contextDoc.args.join(' ')}\`` : ''

          // if found, remember that this Hook is enabled globally
          if (h.context === 'global' && server.controllers.get('DiscordController').botOwner === message.author.id) {
            server.controllers.get('DiscordController').buildEmbedFieldValues(globalEmbedFieldValues, `\n\`${h.name}\` - ${h.description}\n${args}`)

            continue
          }

          // if found, remember that this Hook is enabled for this Guild
          if (message.guild.id === c) {
            server.controllers.get('DiscordController').buildEmbedFieldValues(guildEmbedFieldValues, `\n\`${h.name}\` - ${h.description}\n${args}`)
          }

          // if found, remember that this Hook is enabled for this channel
          if (message.channel.id === c) {
            server.controllers.get('DiscordController').buildEmbedFieldValues(channelEmbedFieldValues, `\n\`${h.name}\` - ${h.description}\n${args}`)
          }
        }
      }

      // if there are enabled Hooks, announce them
      if ([...globalEmbedFieldValues, ...guildEmbedFieldValues, ...channelEmbedFieldValues].length > 0) {
        const globalEmbedFields = server.controllers.get('DiscordController').buildEmbedFields('Enabled Global Hooks', globalEmbedFieldValues)

        const guildEmbedFields = server.controllers.get('DiscordController').buildEmbedFields('Enabled Server Hooks', guildEmbedFieldValues)

        const channelEmbedFields = server.controllers.get('DiscordController').buildEmbedFields('Enabled Channel Hooks', channelEmbedFieldValues)

        // build message embed
        const embed = server.controllers.get('DiscordController').buildEmbed({
          title: HOOK_LIST_TXT,
          footerTextType: 'Command',
          fields: [...globalEmbedFields, ...guildEmbedFields, ...channelEmbedFields]
        })

        // send message embed
        return message.channel.send(embed)

      // otherwise, provide help about enabling Hooks
      } else {
        message.channel.send(`There are no Hooks enabled here. ${HOOK_LIST_TXT}`)
      }
    // if the argument provided is 'list', show a list of Hooks that can be enabled for this context
    } else if (args[0].toLowerCase() === 'list') {
      const globalEmbedFieldValues = []
      const guildEmbedFieldValues = []
      const channelEmbedFieldValues = []

      // get all loaded Hooks and their database documents
      for (const h of server.hooks.values()) {
        // fetch this Hook's database document
        let doc = await server.controllers.get('DatabaseController').fetchDoc({ db: 'hook', id: h.name })

        // if missing, initialize this Hook's database document
        doc = await initializeIfMissing(h, doc)

        if (h.context === 'guild') {
          // determine if context is enabled
          const contextDoc = await server.controllers.get('DatabaseController').fetchDoc({
            db: 'hook',
            id: `${h.name}_${message.guild.id}`
          })
          if (contextDoc.enabled) continue

          // if the Guild Hook is not already enabled, add it to the embed
          server.controllers.get('DiscordController').buildEmbedFieldValues(guildEmbedFieldValues, `\n\`${h.name}\` - ${h.description}`)
        }

        if (h.context === 'channel') {
          // determine if context is enabled
          const contextDoc = await server.controllers.get('DatabaseController').fetchDoc({
            db: 'hook',
            id: `${h.name}_${message.channel.id}`
          })
          if (contextDoc.enabled) continue

          // if the channel Hook is not already enabled, add it to the embed
          server.controllers.get('DiscordController').buildEmbedFieldValues(channelEmbedFieldValues, `\n\`${h.name}\` - ${h.description}`)
        }

        if (h.context === 'global' && server.controllers.get('DiscordController').botOwner === message.author.id) {
          const contextDoc = await server.controllers.get('DatabaseController').fetchDoc({
            db: 'hook',
            id: `${h.name}_global`
          })
          if (contextDoc.enabled) continue

          // if the global Hook is not already enabled, add it to the embed
          server.controllers.get('DiscordController').buildEmbedFieldValues(globalEmbedFieldValues, `\n\`${h.name}\` - ${h.description}`)
        }
      }

      // if there are Hooks that can be enabled, announce them
      if ([...globalEmbedFieldValues, ...guildEmbedFieldValues, ...channelEmbedFieldValues].length > 0) {
        const globalEmbedFields = server.controllers.get('DiscordController').buildEmbedFields('Available Global Hooks', globalEmbedFieldValues)

        const guildEmbedFields = server.controllers.get('DiscordController').buildEmbedFields('Available Server Hooks', guildEmbedFieldValues)

        const channelEmbedFields = server.controllers.get('DiscordController').buildEmbedFields('Available Channel Hooks', channelEmbedFieldValues)

        // build message embed
        const embed = server.controllers.get('DiscordController').buildEmbed({
          title: HOOK_TXT,
          footerTextType: 'Command',
          fields: [...globalEmbedFields, ...guildEmbedFields, ...channelEmbedFields]
        })

        // send message embed
        return message.channel.send(embed)

      // otherwise, announce none available
      } else {
        message.channel.send(`There are no Hooks that can be enabled here. ${HOOK_TXT}`)
      }
    // otherwise, toggle the provided Hook
    } else {
      const hook = server.hooks.get(args[0])

      // stop if Hook not found
      if (!hook) return message.reply('that Hook does not exist.')

      // stop if global Hook is not being toggled by bot owner
      if (server.controllers.get('DiscordController').botOwner !== message.author.id && hook.context === 'global') return message.reply('you cannot do that.')

      let msg

      try {
        // fetch this Hook's database document
        let hookData = await server.controllers.get('DatabaseController').fetchDoc({ db: 'hook', id: hook.name })

        // if missing, initialize this Hook's database document
        hookData = await initializeIfMissing(hook, hookData)

        // determine this Hook's context
        const fetchType = (hook.context === 'guild' ? message.guild.id : message.channel.id)
        const context = {
          type: hook.context,
          id: (hook.context === 'global' ? 'global' : fetchType)
        }

        // if missing, initialize the context's database document for this Hook
        const contextDoc = await server.controllers.get('DatabaseController').fetchDoc({ db: 'hook', id: `${hook.name}_${context.id}` })
        if (contextDoc.status === 404) {
          await server.controllers.get('DatabaseController').updateDoc({
            db: 'hook',
            id: `${hook.name}_${context.id}`
          })
        }

        // if Hook is enabled for this context, disable it
        if (contextDoc.enabled) {
          // do any Hook-specific cleanup before disabling the Hook for this context
          if (context.type === 'guild') {
            await hook.disable({
              server,
              context: await server.controllers.get('DiscordController').client.guilds.fetch(context.id),
              args: args.slice(1)
            })
          } else if (context.type === 'channel') {
            await hook.disable({
              server,
              context: await server.controllers.get('DiscordController').client.channels.fetch(context.id),
              args: args.slice(1)
            })
          } else if (context.type === 'global') {
            await hook.disable({
              server,
              context: 'global',
              args: args.slice(1)
            })
          }

          // save the Hook as disabled in the context's database document
          await server.controllers.get('DatabaseController').updateDoc({
            db: 'hook',
            id: `${hook.name}_${context.id}`,
            payload: {
              enabled: false,
              args: undefined
            }
          })

          // remove the context from the Hook's database document
          if (hookData.contexts.find(p => context.id)) {
            const payload = hookData.contexts

            server.controllers.get('DatabaseController').updateDoc({
              db: 'hook',
              id: hook.name,
              payload: {
                contexts: payload.filter(p => !context.id)
              }
            })
          }

          // announce successfully disabling
          const contextNameFixed = (context.type === 'guild' ? 'server' : 'channel')
          msg = `The \`${hook.name}\` Hook is now **disabled** ${context.type === 'global' ? 'globally' : 'for this ' + contextNameFixed}.`
          message.channel.send(msg)
          msg = `${hook.name} Hook is now disabled ${context.type === 'global' ? 'globally' : `for ${server.utils.capitalize(context.type)}<${context.id}>`} @ ${new Date().toLocaleString()} PT`
          console.log(msg)

        // otherwise, enable the Hook for this channel
        } else {
          // do any Hook-specific setup before enabling the Hook for this context
          if (context.type === 'guild') {
            await hook.enable({
              server,
              context: await server.controllers.get('DiscordController').client.guilds.fetch(context.id),
              args: args.slice(1)
            })
          } else if (context.type === 'channel') {
            await hook.enable({
              server,
              context: await server.controllers.get('DiscordController').client.channels.fetch(context.id),
              args: args.slice(1)
            })
          } else if (context.type === 'global') {
            await hook.disable({
              server,
              context: 'global',
              args: args.slice(1)
            })
          }

          // add the context to the Hook's database document
          if (!hookData.contexts.find(p => context.id)) {
            const payload = hookData.contexts

            server.controllers.get('DatabaseController').updateDoc({
              db: 'hook',
              id: hook.name,
              payload: {
                contexts: [...payload, context.id]
              }
            })
          }

          // save the Hook as enabled in the context's database document
          await server.controllers.get('DatabaseController').updateDoc({
            db: 'hook',
            id: `${hook.name}_${context.id}`,
            payload: {
              enabled: true,
              args: args.slice(1)
            }
          })

          // announce successfully enabling
          const contextNameFixed = (context.type === 'guild' ? 'server' : 'channel')
          msg = `The \`${hook.name}\` Hook is now **enabled** ${context.type === 'global' ? 'globally' : 'for this ' + contextNameFixed}.`
          message.channel.send(msg)
          msg = `${hook.name} Hook is now enabled ${context.type === 'global' ? 'globally' : `for ${server.utils.capitalize(context.type)}<${context.id}>`} @ ${new Date().toLocaleString()} PT`
          console.log(msg)
        }
      } catch (e) {
        msg = `An error occurred while toggling \`${hook.name}\` Hook.`
        message.channel.send(msg)
        msg = `An error occurred in Channel<${message.channel.id}> while toggling ${hook.name} @ ${new Date().toLocaleString()} PT`
        console.log(msg, e)
      }
    }
  }
}