Console Importer

Easily import JS and CSS resources from Chrome console.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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
  }
})()