hooks/ko-fi.js

const joi = require('joi')

/**
 * Converts a {@link https://ko-fi.com/|Ko-fi} webhook into a {@link https://discord.js.org/#/docs/main/stable/class/MessageEmbed|discord.js MessageEmbed} and posts it in a channel
 *
 * `POST: /hook/ko-fi/{channel}`
 *
 * @type {Hook}
 * @memberof Hook
 * @name ko-fi
 * @prop {Object} initConfig `init` function configuration object
 * @prop {Esdi} initConfig.server Esdi server instance
 * @tutorial ko-fi-channel-hook-example
 * @static
 */
module.exports = {
  name: 'ko-fi',
  context: 'channel',
  description: 'Converts a Ko-fi webhook into a message embed and posts it in a channel.',
  init (server) {
    return {
      method: 'POST',
      path: '/hook/ko-fi/{channel}',
      handler: async (request, h) => {
        // check if channel exists and is enabled
        const checkChannel = await this.checkEnabledForContext({
          h,
          server,
          contextId: request.params.channel,
          contextType: 'channel'
        })
        if (!checkChannel.id) {
          return checkChannel
        }

        let msg

        // create a webhook
        const channelHook = await checkChannel.createWebhook(
          'Ko-fi',
          { avatar: 'https://user-images.githubusercontent.com/7295363/99930265-49bad700-2d05-11eb-9057-1a013c45ee2c.png' }
        )

        // validate payload
        const kofiSchema = joi.object({
          message_id: joi.string()
            .length(36),
          timestamp: joi.date(),
          type: joi.string()
            .pattern(/(Donation|Commission|Shop Order)/),
          is_public: joi.boolean(),
          from_name: joi.string(),
          message: joi.string()
            .allow(''),
          amount: joi.string(),
          url: joi.string()
            .uri(),
          email: joi.string()
            .allow(null),
          currency: joi.string()
            .allow(null),
          is_subscription_payment: joi.boolean(),
          is_first_subscription_payment: joi.boolean(),
          kofi_transaction_id: joi.string()
        })
        if (!request.payload ||
              !request.payload.data ||
              kofiSchema.validate(JSON.parse(request.payload.data)).error) {
          msg = `Ko-fi Hook for Channel<${request.params.channel}> was rejected due to invalid payload @ ${new Date().toLocaleString()} PT`
          console.log(msg, kofiSchema.validate(JSON.parse(request.payload.data)))
          return h.response(msg).code(400)
        }

        // parse payload
        const parsed = JSON.parse(request.payload.data)

        // check if payload is private
        let isPrivate
        if (parsed.is_public === false) {
          parsed.from_name = 'Anonymous'
          parsed.message = ''

          msg = `Private ko-fi Hook for Channel<${request.params.channel}> was handled @ ${new Date().toLocaleString()} PT`
          isPrivate = true
        }

        // prepare fields for message embed
        const embedFields = [
          {
            name: '**Name**',
            value: parsed.from_name,
            inline: true
          },
          {
            name: '**Amount**',
            value: donationAmount(parsed),
            inline: true
          }
        ]
        if (parsed.message.length > 0) {
          embedFields.push({
            name: '**Message**',
            value: parsed.message
          })
        }

        // build message embed
        const embed = server.controllers.get('DiscordController').buildEmbed({
          title: '☕ New Ko-fi contribution',
          footerTextType: 'Hook',
          url: parsed.url,
          fields: embedFields,
          hexColor: '#29ABE0',
          thumbnail: {
            url: 'https://user-images.githubusercontent.com/7295363/99930265-49bad700-2d05-11eb-9057-1a013c45ee2c.png'
          }
        })

        // if webhook is public, set result message
        if (!isPrivate) {
          msg = `Ko-fi Hook for Channel<${request.params.channel}> was handled @ ${new Date().toLocaleString()} PT`
        }

        // send the message embed
        channelHook.send({ embeds: [embed] }).then(() => {
          // deletes the webhook after use
          channelHook.delete()
        })

        // announce handling the request successfully
        console.log(msg)
        return h.response(msg).code(200)
      }
    }
  }
}

// helper function that returns the donation amount with a currency symbol
const donationAmount = (parsed) => {
  return `${parsed.currency ? parsed.currency : '$'}${parsed.amount}`
}