Console Importer

Easily import JS and CSS resources from Chrome console.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @author       Lete114
// @version      0.1
// @license      MIT License
// @name         Console Importer
// @description  Easily import JS and CSS resources from Chrome console.
// @match        *://*/*
// @icon         data:image/png;base64,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
// @namespace    https://github.com/Lete114/my-tampermonkey-scripts
// @grant        unsafeWindow
// ==/UserScript==

;(function () {
  'use strict'
  // https://github.com/pd4d10/console-importer
  const window = unsafeWindow
  const tiza = new Tiza()
  const PREFIX_TEXT = '[$i]: '
  const prefix = tiza.color('blue').text
  const strong = tiza.color('blue').bold().text
  const error = tiza.color('red').text
  const log = (...args) => tiza.log(prefix(PREFIX_TEXT), ...args)
  const logError = (...args) => tiza.log(error(PREFIX_TEXT), ...args)

  /**
   * @type { Set<string> | null }
   */
  let lastGlobalVariableSet = null

  /**
   *
   * @param { string } name
   * @returns
   */
  function createBeforeLoad(name) {
    return () => {
      lastGlobalVariableSet = new Set(Object.keys(window))
      log(strong(name), ' is loading, please be patient...')
    }
  }

  /**
   *
   * @param { string } name
   * @param { string? } url
   * @returns
   */
  function createOnLoad(name, url) {
    return () => {
      const urlText = url ? `(${url})` : ''
      log(strong(name), `${urlText} is loaded.`)

      const currentGlobalVariables = Object.keys(window)
      const newGlobalVariables = currentGlobalVariables.filter(
        (key) => !lastGlobalVariableSet?.has(key)
      )
      if (newGlobalVariables.length > 0) {
        log(
          'The new global variables are as follows: ',
          strong(newGlobalVariables.join(',')),
          ' . Maybe you can use them.'
        )
      } else {
        // maybe css request or script loaded already
      }
      // Update global variable list
      lastGlobalVariableSet = new Set(currentGlobalVariables)
    }
  }

  /**
   *
   * @param { string } name
   * @param { string? } url
   * @returns
   */
  function createOnError(name, url) {
    return () => {
      const urlText = url ? `(${strong(url)})` : ''
      logError(
        'Fail to load ',
        strong(name),
        ', is this URL',
        urlText,
        ' correct?'
      )
    }
  }

  // Try to remove referrer for security
  // https://imququ.com/post/referrer-policy.html
  // https://www.w3.org/TR/referrer-policy/
  function addNoReferrerMeta() {
    const originMeta =
      document.querySelector < HTMLMetaElement > 'meta[name=referrer]'

    if (originMeta) {
      // If there is already a referrer policy meta tag, save origin content
      // and then change it, call `remove` to restore it
      const content = originMeta.content
      originMeta.content = 'no-referrer'
      return function remove() {
        originMeta.content = content
      }
    } else {
      // Else, create a new one, call `remove` to delete it
      const meta = document.createElement('meta')
      meta.name = 'referrer'
      meta.content = 'no-referrer'
      document.head.appendChild(meta)
      return function remove() {
        // Removing meta tag directly not work, should set it to default first
        meta.content = 'no-referrer-when-downgrade'
        document.head.removeChild(meta)
      }
    }
  }

  /**
   * Insert script tag
   * @param { sting } url
   * @param { function } onload
   * @param { function } onerror
   */
  function injectScript(url, onload, onerror) {
    const remove = addNoReferrerMeta()
    const script = document.createElement('script')
    script.src = url
    script.onload = onload
    script.onerror = onerror
    document.body.appendChild(script)
    remove()
    document.body.removeChild(script)
  }

  /**
   * Insert link tag
   * @param { string } url
   * @param { function } onload
   * @param { function } onerror
   */
  function injectStyle(url, onload, onerror) {
    const remove = addNoReferrerMeta()
    const link = document.createElement('link')
    link.href = url
    link.rel = 'stylesheet'
    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#Stylesheet_load_events
    link.onload = onload
    link.onerror = onerror
    document.head.appendChild(link)
    remove()
    // Should not remove <link> tag, unlike <script>
  }

  /**
   *
   * @param { string } url
   * @param {*} beforeLoad
   * @param {*} onload
   * @param {*} onerror
   * @returns
   */
  function inject(
    url,
    beforeLoad = createBeforeLoad(url),
    onload = createOnLoad(url),
    onerror = createOnError(url)
  ) {
    beforeLoad()

    // Handle CSS
    if (/\.css$/.test(url)) {
      return injectStyle(url, onload, onerror)
    }

    // Handle JS
    return injectScript(url, onload, onerror)
  }

  /**
   * From cdnjs
   * https://cdnjs.com/
   * @param { string } name
   */
  function cdnjs(name) {
    log('Searching for ', strong(name), ', please be patient...')
    fetch(`https://api.cdnjs.com/libraries?search=${name}`, {
      referrerPolicy: 'no-referrer'
    })
      .then((res) => res.json())
      .then(({ results }) => {
        if (results.length === 0) {
          logError(
            'Sorry, ',
            strong(name),
            ' not found, please try another keyword.'
          )
          return
        }

        const matchedResult = results.filter((item) => item.name === name)
        const { name: exactName, latest: url } = matchedResult[0] || results[0]
        if (name !== exactName) {
          log(
            strong(name),
            ' not found, import ',
            strong(exactName),
            ' instead.'
          )
        }

        inject(
          url,
          createBeforeLoad(exactName),
          createOnLoad(exactName, url),
          createOnError(exactName, url)
        )
      })
      .catch(() => {
        logError(
          'There appears to be some trouble with your network. If you think this is a bug, please report an issue:'
        )
        logError('https://github.com/pd4d10/console-importer/issues')
      })
  }

  /**
   * From unpkg
   * https://unpkg.com
   * @param { string } name
   */
  function unpkg(name) {
    createBeforeLoad(name)()
    const url = `https://unpkg.com/${name}`
    injectScript(url, createOnLoad(name, url), createOnError(name, url))
  }

  /**
   * https://www.jsdelivr.com/esm
   * @param { string } name
   * @returns
   */
  async function esm(name) {
    log(strong(name), '(esm) is loading, please be patient...')
    const res = await import(`https://esm.run/${name}`)
    return res
  }

  /**
   * Entry
   * @param { string } originName
   * @returns
   */
  function importer(originName) {
    if (typeof originName !== 'string') {
      throw new Error('Argument should be a string, please check it.')
    }

    // Trim string
    const name = originName.trim()

    // If it is a valid URL, inject it directly
    if (/^https?:\/\//.test(name)) {
      return inject(name)
    }

    // If version specified, try unpkg
    if (name.indexOf('@') !== -1) {
      return unpkg(name)
    }

    return cdnjs(name)
  }

  importer.cdnjs = cdnjs
  importer.unpkg = unpkg
  importer.esm = esm

  // Do not output annoying ugly string of function content
  importer.toString = () => '$i'

  // Assign to console
  console.$i = importer

  // Do not break existing $i
  const win = window
  if (typeof win.$i === 'undefined') {
    win.$i = importer
  } else {
    log('$i is already in use, please use `console.$i` instead')
  }

  function Tiza(currentStyles, texts, styles) {
    if (currentStyles === void 0) {
      currentStyles = []
    }
    if (texts === void 0) {
      texts = []
    }
    if (styles === void 0) {
      styles = []
    }
    var _this = this
    // Get method
    this.getCurrentStyles = function () {
      return _this._currentStyles
    }
    this.getTexts = function () {
      return _this._texts
    }
    this.getStyles = function () {
      return _this._styles
    }
    // Push a style to current Styles
    this.style = function (s) {
      return new Tiza(
        _this._currentStyles.concat([s]),
        _this._texts,
        _this._styles
      )
    }
    // Alias for style method
    this.color = function (c) {
      return _this.style('color:' + c)
    }
    this.bgColor = function (c) {
      return _this.style('background-color:' + c)
    }
    this.bold = function () {
      return _this.style('font-weight:bold')
    }
    this.italic = function () {
      return _this.style('font-style:italic')
    }
    this.size = function (n) {
      var s = typeof n === 'number' ? n + 'px' : n // Convert number to px
      return _this.style('font-size:' + s)
    }
    // Clear all current styles
    this.reset = function () {
      return new Tiza([], _this._texts, _this._styles)
    }
    this.text = function () {
      var args = []
      for (var _i = 0; _i < arguments.length; _i++) {
        args[_i] = arguments[_i]
      }
      var texts = _this._texts.slice()
      var styles = _this._styles.slice()
      args.forEach(function (arg) {
        if (arg instanceof Tiza) {
          texts.push.apply(texts, arg.getTexts())
          styles.push.apply(styles, arg.getStyles())
        } else {
          texts.push(arg)
          styles.push(_this._currentStyles.join(';'))
        }
      })
      return new Tiza(_this._currentStyles, texts, styles)
    }
    this.space = function (count) {
      if (count === void 0) {
        count = 1
      }
      return _this.text(repeat(' ', count))
    }
    this.newline = function (count) {
      if (count === void 0) {
        count = 1
      }
      return _this.text(repeat('\n', count))
    }
    this._output = function (type) {
      return function () {
        var args = []
        for (var _i = 0; _i < arguments.length; _i++) {
          args[_i] = arguments[_i]
        }
        var ins = _this.text.apply(_this, args)
        console[type].apply(
          console,
          [
            ins
              .getTexts()
              .map(function (t) {
                return '%c' + t
              })
              .join('')
          ].concat(ins._styles)
        )
        return new Tiza(ins.getCurrentStyles())
      }
    }
    this.log = this._output('log')
    this.info = this._output('info')
    this.warn = this._output('warn')
    this.error = this._output('error')
    this._currentStyles = currentStyles
    this._texts = texts
    this._styles = styles
  }
})()