struct/EmoteParser.js

import Constants from '../util/Constants.js'

class EmoteParser {
  /**
   * A parser to replace text with emotes.
   * @param {EmoteFetcher} fetcher - The fetcher to use the cache of.
   * @param {object} [options={}] - Options for the parser.
   * @param {string} [options.template=''] - The template to be used.
   * The strings that can be interpolated are:
   * - `{link}` The link of the emote.
   * - `{name}` The name of the emote.
   * - `{size}` The size index of the image.
   * - `{creator}` The channel/owner name of the emote.
   * @param {'html' | 'markdown' | 'bbcode' | 'plain'} [options.type='html'] - The type of the parser.
   * Can be one of `html`, `markdown`, `bbcode`, or `plain`.
   * If the `template` option is provided, this is ignored.
   * @param {RegExp} [options.match=/(\w+)/g] - The regular expression that matches an emote.
   * Must be a global regex, with one capture group for the emote code.
   */
  constructor (fetcher, options = {}) {
    /**
     * The emote fetcher being used.
     * @type {EmoteFetcher}
     */
    this.fetcher = fetcher

    /**
     * The parser options.
     * @type {object}
     */
    this.options = {
      template: '',
      type: 'html',
      match: /(\w+)/g,
      ...options,
    }

    this._validateOptions(this.options)
  }

  /**
   * Validates the parser options.
   * @private
   * @param {object} [options] - Options for the parser.
   * @param {string} [options.template] - The template to be used.
   * The strings that can be interpolated are:
   * - `{link}` The link of the emote.
   * - `{name}` The name of the emote.
   * - `{size}` The size of the image.
   * - `{creator}` The channel/owner name of the emote.
   * @param {'html' | 'markdown' | 'bbcode' | 'plain'} [options.type] - The type of the parser.
   * Can be one of `html`, `markdown`, `bbcode`, or `plain`.
   * If the `template` option is provided, this is ignored.
   * @param {RegExp} [options.match] - The regular expression that matches an emote.
   * Must be a global regex, with one capture group for the emote code.
   * @throws {TypeError} When template is not a string.
   * @throws {TypeError} When type is not one of the supported types.
   * @throws {TypeError} When match is not a global RegExp.
   */
  _validateOptions (options) {
    if (options.template && typeof options.template !== 'string') {
      throw new TypeError('Template must be a string')
    }

    if (!['html', 'markdown', 'bbcode', 'plain'].includes(options.type)) {
      throw new TypeError('Parse type must be one of `html`, `markdown`, `bbcode`, or `plain`')
    }

    if (!(options.match instanceof RegExp) || !options.match.global) {
      throw new TypeError('Match must be a global RegExp.')
    }
  }

  /**
   * Parses text.
   * @param {string} text - Text to parse.
   * @param {object} [options] - Parameters for parsing.
   * @param {number} [options.size] - Size (scale) for emotes.
   * @param {boolean} [options.forceStatic] - Whether to force the emote to be static (non-animated). Defaults to the fetcher's forceStatic or `false`.
   * @param {'dark' | 'light'} [options.themeMode] - Only for Twitch: the preferred theme mode. Defaults to the fetcher's twitchThemeMode or `dark`.
   * @returns {string} - The parsed text.
   */
  parse (text, options) {
    // @NOTE(kody): Not setting defaults here, they'll be handled by each emote's toLink method.
    const {
      size,
      forceStatic,
      themeMode,
    } = options || {}

    const parsed = text.replace(this.options.match, (matched, id) => {
      const emote = this.fetcher.emotes.get(id)
      if (!emote) return matched
      if (emote.modifier) return ''

      const template = this.options.template || Constants.Templates[this.options.type]
      const link = emote.toLink({ size, forceStatic, themeMode })
      const res = template
        .replaceAll('{link}', link)
        .replaceAll('{name}', emote.code)
        .replaceAll('{size}', size)
        .replaceAll('{creator}', emote.ownerName || 'global')

      return res
    })

    return parsed
  }
}

export default EmoteParser