Xueersi Aurora

Xueersi enhancement | For the old days of Xueersi.

目前为 2024-08-07 提交的版本。查看 最新版本

// ==UserScript==
// @name               Xueersi Aurora
// @name:zh-cn         学而思极光
// @namespace          XueersiAurora
// @version            1.0
// @description        Xueersi enhancement | For the old days of Xueersi.
// @description:zh-cn  学而思增强工具 | 谨以此献给学而思最好的那段时光, 以及我们失去的一切。
// @license            AGPL-3.0-only
// @author             FurryR
// @match              https://code.xueersi.com/*
// @icon               https://static0.xesimg.com/platform-fe/website/home/favicon.ico
// @grant              none
// @run-at             document-start
// ==/UserScript==

;(() => {
  'use strict'

  /**
   * @typedef {{ new(tag?: string, data?: object, children?: object[], text?: string, elm?: unknown, context?: unknown, componentOptions?: object, asyncFactory?: Function): object }} VElement
   */

  const logger = Object.assign({}, console)

  if (!window.location.hostname.includes('xueersi.com')) {
    logger.log(`Aurora 将不在 ${window.location.href} 内运行。`)
    return
  }

  if (!console.log.toString().includes('[native code]')) {
    alert(
      '看起来 Xueersi Aurora 加载得太慢了... 别担心,我们可以轻松解决这个问题。\n1. 转到 Tampermonkey 设置。\n2. 将“配置模式”改为“高级”。\n3. 转到“实验”,将“注入模式”改为“即时”。\n4. 刷新页面。\n\n在您进行这些操作前,Xueersi Aurora 的部分或全部功能不会生效。'
    )
  }

  // [审查用]取得直链
  function getScratchlink(id, version, type) {
    let ret = ''
    if (type == 'scratch') {
      ret = `https://code.xueersi.com/scratch/index.html?pid=${id}&version=${version}&env=community`
      if (version == '2.0') {
        if (id.includes('8080')) {
          ret = `http://dev-code.xueersi.com/scratch/index.html?pid=${id}&version=${version}&env=community`
        }
      } else {
        if (id.includes('8080')) {
          ret = `http://dev-code.xueersi.com/scratch3/index.html?pid=${id}&version=${version}&env=community`
        } else {
          ret = `https://code.xueersi.com/scratch3/index.html?pid=${id}&version=${version}&env=community`
        }
      }
    } else if (type == 'cpp' || type == 'webpy' || type == 'python') {
      ret = `https://code.xueersi.com/ide/code/${id}`
    }
    return ret
  }

  // xterm-original from xtermjs.org
  const xterm_theme = {
    foreground: '#F8F8F8',
    background: '#2D2E2C',
    selection: '#5DA5D533',
    black: '#1E1E1D',
    brightBlack: '#262625',
    red: '#CE5C5C',
    brightRed: '#FF7272',
    green: '#5BCC5B',
    brightGreen: '#72FF72',
    yellow: '#CCCC5B',
    brightYellow: '#FFFF72',
    blue: '#5D5DD3',
    brightBlue: '#7279FF',
    magenta: '#BC5ED1',
    brightMagenta: '#E572FF',
    cyan: '#5DA5D5',
    brightCyan: '#72F0FF',
    white: '#F8F8F8',
    brightWhite: '#FFFFFF'
  }

  const CODEFONT_CSS =
    'color: white; font-family: "Jetbrains Mono", "Fira Code", Consolas, "Courier New", monospace'

  function patch(obj, p, fn) {
    if (obj[p]) obj[p] = fn(obj[p])
  }
  /**
   * 获得默认配置。
   * @returns 默认配置。
   */
  function default_config() {
    return {
      disabled: [],
      option: {}
    }
  }
  let Aurora_config = default_config()
  class PluginManager {
    /**
     * 获得名为 id 的设置,若找不到则以 value 顶替。
     * @param {id} id 设置 id。
     * @param {any} value 顶替的值。
     * @returns {any} 设置的值。
     */
    get_option_or(id, value) {
      if (Aurora_config.option[id] !== undefined) {
        return Aurora_config.option[id]
      }
      Aurora_config.option[id] = value
      window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config))
      return value
    }
    /**
     * 重设 ID 为 id 的设置为指定值。
     * @param {string} id 设定 id。
     * @param {any} value 设定的值。
     */
    set_option(id, value) {
      Aurora_config.option[id] = value
      window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config))
    }
    /**
     * 注册(不可用)功能。
     * @param {string} id 功能的 ID。
     * @param {string} description 功能的人类可读描述。
     * @param {Function} fn 功能本体函数。
     */
    plug(id, description, fn) {
      this._events[id] = [description, fn]
    }
    /**
     * 完成注册(不可用),开始加载 Aurora。
     */
    done() {
      const v = Aurora_config.disabled
      if (!v instanceof Array) {
        logger.error('Aurora 加载失败。请检查配置文件是否设置正确。')
        return
      }
      for (const [key, value] of Object.entries(this._events)) {
        if (!v.includes(key)) {
          if (window === window.top)
            logger.log(`Aurora 正在加载功能 %c${key}%c。`, CODEFONT_CSS, '')
          value[1]()
        }
      }
    }
    constructor() {
      this._events = {}
    }
  }
  class VueElementMixin {
    constructor() {
      this._events = new Map()
    }
    on(tagName, fn) {
      const v = this._events.get(tagName)
      if (v) v.push(fn)
      else this._events.set(tagName, [fn])
    }
    emit(instance) {
      const tag =
        instance.$vnode?.componentOptions?.tag ??
        instance._vnode?.componentOptions?.tag ??
        instance.$vnode?.tag?.split('-')?.at(-1)
      if (this._events.has(tag)) {
        for (const v of this._events.get(tag)) {
          try {
            v(instance)
          } catch (e) {
            logger.error(
              `在为 %c${tag}%c 组件进行补丁时发生了错误。这看起来是一个漏洞,请报告给 Aurora 开发者。`,
              CODEFONT_CSS,
              ''
            )
            logger.error(e)
          }
        }
      }
    }
  }
  const webpackListener = []
  const plug = new PluginManager()
  window.Aurora = {
    /**
     * 获得帮助。
     * @param {string | undefined} 帮助 ID。不填则查看全部。
     */
    help(obj = undefined) {
      if (obj == undefined) {
        logger.log('Aurora 帮助')
        logger.log('所有条目:')
        logger.table({
          enable_plugin: '关于启用/关闭功能的方法',
          option: '关于设置',
          development: '关于如何开发功能'
        })
        logger.log(
          '请输入 %clogger.log("条目 ID")%c 来查看相应条目的帮助。',
          CODEFONT_CSS,
          ''
        )
      } else if (obj == 'enable_plugin') {
        logger.log(
          '以下是 Aurora 对象中 %cenable/disable%c 方法参数的说明。',
          CODEFONT_CSS,
          ''
        )
        logger.table({
          id: '可选,需启用/禁用功能的 ID。若不指定,则会打印目前启用/禁用的全部功能。'
        })
      } else if (obj == 'option') {
        logger.log(
          '%cAurora.option%c 可以获取/更改设置内容。',
          CODEFONT_CSS,
          ''
        )
        logger.log('以下是 Aurora 对象中 option 方法参数的说明。')
        logger.table({
          id: '可选,需设定的设置 id。不指定时,打印全部设置。',
          value: '可选,需设定的设置值。不指定时,返回这个 id 的设置。'
        })
      } else if (obj == 'development') {
        logger.log('%cplug.plug%c 可以注册(不可用)一个功能。', CODEFONT_CSS, '')
        logger.log('以下是 plug 对象中 plug 方法参数的说明。')
        logger.table({
          id: '功能的 ID。',
          description: '功能的人类可读描述。',
          fn: '功能本体函数。'
        })
      } else {
        logger.log('无法找到条目。请确认您的拼写。')
      }
    },
    /**
     * 禁用功能。当没有参数时,查看已经禁用的功能列表。
     * @param {string[]} args 禁用的功能 ID。
     */
    disable(...args) {
      if (args.length == 0) {
        {
          const v = Aurora_config.disabled,
            v2 = {}
          if (!v instanceof Array) {
            logger.error('Aurora 加载失败。请检查配置文件是否设置正确。')
            return
          }
          if (v.length != 0) {
            logger.log('已经禁用的功能:')
            for (const key of v) {
              v2[key] = plug._events[key][0]
            }
            logger.table(v2)
          } else {
            logger.log('尚未禁用任何功能。')
          }
        }
        return
      }
      for (const id of args) {
        if (!Aurora_config.disabled.includes(id)) {
          Aurora_config.disabled.push(id)
        }
        window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config))
        logger.log(`Aurora 已经禁用了 %c${id}%c。`, CODEFONT_CSS, '')
      }
      logger.log('将在 %c3s%c 后刷新网页以应用更改。', CODEFONT_CSS, '')
      setTimeout(() => window.location.reload(), 3000)
    },
    /**
     * 启用功能。
     * @param {string[]} args 启用的功能 ID。
     */
    enable(...args) {
      if (args.length == 0) {
        {
          const v = Aurora_config.disabled,
            v2 = {}
          if (!v instanceof Array) {
            logger.error('Aurora 加载失败。请检查配置文件是否设置正确。')
            return
          }
          if (v.length != Object.keys(plug._events).length) {
            logger.log('已经启用的功能:')
            for (const [key, value] of Object.entries(plug._events)) {
              if (!v.includes(key)) {
                v2[key] = value[0]
              }
            }
            logger.table(v2)
          } else {
            logger.log('尚未启用任何功能。')
          }
        }
        return
      }
      for (const id of args) {
        Aurora_config.disabled = Aurora_config.disabled.filter(val => val != id)
        window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config))
        logger.log(`Aurora 已经启用了 %c${id}%c。`, CODEFONT_CSS, '')
      }
      logger.log('将在 %c3s%c 后刷新网页以应用更改。', CODEFONT_CSS, '')
      setTimeout(() => window.location.reload(), 3000)
    },
    /**
     * 获得 Aurora 设置。
     * @param {string | undefined} id 设置的 ID。
     * @param {string | undefined} value 设置的值。
     */
    option(...args) {
      if (args.length == 0) {
        logger.log('Aurora 设置:')
        logger.table(Aurora_config.option)
        return
      }
      if (args.length == 1) {
        return Aurora_config.option[args[0]]
      }
      Aurora_config.option[args[0]] = args[1]
      window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config))
      logger.log(
        `Aurora 已经设置了 %c${args[0]}%c 为 %c${args[1]}%c。`,
        CODEFONT_CSS,
        '',
        CODEFONT_CSS,
        ''
      )
      logger.log('将在 %c3s%c 后刷新网页以应用更改。', CODEFONT_CSS, '')
      setTimeout(() => window.location.reload(), 3000)
    }
  }
  function requireVue(callback) {
    const call = Function.prototype.call
    let captured = false
    Function.prototype.call = function (self, ...args) {
      if (
        args.length === 3 &&
        typeof args[0] === 'object' &&
        args[0] !== null &&
        typeof args[1] === 'object' &&
        args[1] !== null &&
        typeof args[2] === 'function' &&
        args[0].exports
      ) {
        const fn = this
        // const require = args[2]
        const str = fn.toString()
        if (str.includes('ENABLE_XES_CONSOLE')) {
          return
        }
        const res = call.apply(this, [self, ...args])
        const exports = args[0].exports
        webpackListener.forEach(v => v(exports))
        if (
          typeof exports.default === 'function' &&
          typeof exports.default.version === 'string' &&
          !captured
        ) {
          // This is vue.
          captured = true
          callback(self.default)
        }
        return res
      } else return call.apply(this, [self, ...args])
    }
  }
  function addStyle(css) {
    if (css instanceof URL) {
      const style = document.createElement('link')
      style.rel = 'stylesheet'
      style.href = css.toString()
      document.documentElement.appendChild(style)
    } else {
      const style = document.createElement('style')
      style.textContent = css
      document.documentElement.appendChild(style)
    }
  }
  {
    if (window === window.top) logger.log('Aurora 正在加载配置。')
    let v = window.localStorage.getItem('Aurora')
    if (v == null) {
      window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config))
    } else {
      try {
        Aurora_config = JSON.parse(v)
      } catch (_) {
        window.localStorage.setItem('Aurora', JSON.stringify(Aurora_config))
      }
    }
  }
  const vueMixinManager = new VueElementMixin()
  requireVue(Vue => {
    patch(Vue.prototype, '_init', _init => {
      return function (args) {
        _init.call(this, args)
        vueMixinManager.emit(this)
      }
    })
  })
  plug.plug('privacy', '去除一些让开发者伤心的东西。', () => {
    sessionStorage.setItem('debugger', 'true')
    Object.defineProperty(window, 'logger', {
      get() {
        return new Proxy(
          {},
          {
            get() {
              return () => {}
            },
            set() {
              return true
            }
          }
        )
      },
      set() {}
    })
    Object.defineProperty(window, 'xesWeb_eventLog', {
      get() {
        return new Proxy(
          {},
          {
            get() {
              return () => {}
            },
            set() {
              return true
            }
          }
        )
      },
      set() {}
    })
    Object.defineProperty(window, 'XesLoggerSDK', {
      value: function () {},
      writable: false
    })
  })
  plug.plug('sudo', '非唯C者也是人。', () => {
    vueMixinManager.on('109', instance => {
      instance.fnHandlePermission = () => true
    })
  })
  plug.plug('operate', '作品操作优化。', () => {
    vueMixinManager.on('109', instance => {
      if (instance.projectData) {
        instance.onAdaptClick = () => {
          instance.openNewTab(
            getScratchlink(
              instance.projectId,
              instance.version ?? instance.projectData.version,
              instance.$route.query.langType
            )
          )
        }
        const render = instance.$options.render
        instance.$options.render = function (renderProxy, createElement) {
          this.projectData.hidden_code = 2
          const ret = render.call(this, renderProxy, createElement)
          const operateLeft =
            ret.componentOptions.children[1].children[1].children[1].children[0]
              .children[1].children[0]
          const adapt = operateLeft.children[5]
          adapt.children[1].text = ' 审查 '
          return ret
        }
      }
    })
  })
  plug.plug('unlimited', '解除一些长度限制。', () => {
    if (window.location.pathname.startsWith('/space/')) {
      vueMixinManager.on('homepages', instance => {
        const render = instance.$options.render
        instance.$options.render = function (renderProxy, createElement) {
          const res = render.call(this, renderProxy, createElement)
          if (this.contenteditable) {
            const signature =
              res.children[0].componentOptions.children[1].children[2]
                .children[0]
            delete signature.data.attrs.maxlength
          }
          return res
        }
      })
    } else if (window.location.pathname.startsWith('/ide/code')) {
      vueMixinManager.on('HeaderBase', instance => {
        const render = instance.$options.render
        instance.$options.render = function (renderProxy, createElement) {
          const res = render.call(this, renderProxy, createElement)
          if (this.canEditName) {
            const input = res.children[0].children[1].children[0]
            delete input.data.attrs.maxlength
          }
          return res
        }
      })
    } else if (window.location.pathname === '/project/publish/modal') {
      addStyle(`
.publish .publish_main_warp .publish_main .publish_detail .icon-edit {
  font-size: 14px;
  color: rgba(53,53,53,.3);
  cursor: pointer
}
`)
      vueMixinManager.on('Publish', instance => {
        const render = instance.$options.render
        instance.inputHandle = () => {
          instance.descriptionLength = instance.description.length
        }
        instance.$data.textareaPlaceholder = '在这里介绍你的作品...'
        instance.$data.tagPlacerholder =
          '勾选作品的标签,你也可以点击右侧按钮来添加标签。'
        instance.$data.toSelectedTag = tag => {
          if (this.selectedTagList.includes(tag)) {
            const tagIndex = this.selectedTagList.findIndex(
              selectedTag => selectedTag === tag
            )
            this.selectedTagList.splice(tagIndex, 1)
          } else this.selectedTagList.push(tag)
        }
        instance.$options.render = function (renderProxy, createElement) {
          const res = render.call(this, renderProxy, createElement)
          const workName = res.children[0].children[1].children[1].children[1]
          const textArea =
            res.children[0].children[1].children[1].children[7].children[0]
          const helpClick =
            res.children[0].children[1].children[1].children[5].children[0]
              .children[1]
          helpClick.data.on.click = () => {
            const p = prompt('请输入需要添加的标签。')
            if (p && !this.tagList.includes(p)) this.tagList.push(p)
          }
          helpClick.data.staticClass = 'iconfont icon-edit'
          helpClick.data.attrs = { title: '添加自定义标签...' }
          delete workName.children[0].data.attrs.maxlength
          delete textArea.children[0].data.attrs.maxlength
          textArea.children.pop()
          return res
        }
      })
    }
  })
  plug.plug('editor', '各种编辑器优化。', () => {
    if (
      window.location.href.startsWith(
        'https://code.xueersi.com/toolkit/template'
      )
    ) {
      vueMixinManager.on('6', instance => {
        instance.closeModal()
      })
    } else {
      // replace default code
      ;(() => {
        const template = plug.get_option_or('template', {
          cpp: `#include <iostream>

int main() {
  // 这是 Xueersi Aurora 的 C++ 默认模板。你可以在 Aurora 设置中变更它。
  std::cout << "[__AURORA_HITOKOTO__]" << std::endl;
  return 0;
}
`,
          python: `# 这是 Xueersi Aurora 的 Python 默认模板。你可以在 Aurora 设置中变更它。

print("[__AURORA_HITOKOTO__]")
`,
          webpy: `# 这是 Xueersi Aurora 的 Web Python 默认模板。你可以在 Aurora 设置中变更它。

print("[__AURORA_HITOKOTO__]")
`,
          offlinepy: `# 这是 Xueersi Aurora 的离线 Python 默认模板。你可以在 Aurora 设置中变更它。

print("[__AURORA_HITOKOTO__]")
`,
          hw1py: `# 这是 Xueersi Aurora 的光环板 Python 默认模板。你可以在 Aurora 设置中变更它。

print("[__AURORA_HITOKOTO__]")
`,
          hw2py: `# 这是 Xueersi Aurora 的编程掌机 Python 默认模板。你可以在 Aurora 设置中变更它。

print("[__AURORA_HITOKOTO__]")
`
        })
        let loaded = false
        // Universal (Vue.js / React) patch
        webpackListener.push(exports => {
          if (
            typeof exports === 'function' &&
            exports.default === exports &&
            typeof exports.Axios === 'function' &&
            !loaded
          ) {
            loaded = true
            exports.interceptors.response.use(function (resp) {
              const map = {
                1: 'cpp',
                2: 'python',
                3: 'webpy',
                4: 'webpy',
                5: 'hw1py',
                7: 'hw2py',
                28794692: 'offlinepy'
              }
              if (
                resp.config.url.startsWith('/compilers/v2/') ||
                resp.config.url.startsWith('/api/projects/v2/') ||
                resp.config.url.startsWith('/projects/v2/') ||
                (resp.config.url === '/community/v4/projects/detail' &&
                  window.location.pathname.startsWith('/ide/code/'))
              ) {
                if (resp.data.stat === 1) {
                  if (resp.data.data.id in map) {
                    const t = template[map[resp.data.data.id]]
                    if (t.includes('[__AURORA_HITOKOTO__]')) {
                      return fetch('https://v1.hitokoto.cn/?c=k')
                        .then(
                          v => {
                            logger.log(
                              '随机名言来自 Hitokoto,请访问 https://hitokoto.cn/ 以了解更多相关信息。'
                            )
                            return v.json()
                          },
                          () => ({
                            hitokoto:
                              '谨以此献给学而思最好的那段时光, 以及我们失去的一切。',
                            from_who: '凌'
                          })
                        )
                        .then(v => {
                          let hitokoto = ''
                          if (!v.from_who && !v.from) hitokoto = `${v.hitokoto}`
                          else if (!v.from_who)
                            hitokoto = `${v.hitokoto} —— 「${v.from}」`
                          else if (!v.from)
                            hitokoto = `${v.hitokoto} —— ${v.from_who}`
                          else
                            hitokoto = `${v.hitokoto} —— ${v.from_who}「${v.from}」`
                          resp.data.data.xml = t.replace(
                            /\[__AURORA_HITOKOTO__\]/g,
                            hitokoto
                          )
                          return resp
                        })
                    }
                    resp.data.data.xml = template[map[resp.data.data.id]]
                  }
                }
              }
              return resp
            })
          }
        })
      })()
      // no timer
      ;(() => {
        let loaded = false
        vueMixinManager.on('IdeEditor', instance => {
          instance.fnTryLockRun = () => {
            return true
          }
          if (window.Sk && !loaded) {
            loaded = true
            let v = Infinity
            Object.defineProperty(window.Sk, 'execLimit', {
              get: () => v,
              set: val => {
                if (val == 1) {
                  v = 1
                  queueMicrotask(() => {
                    v = Infinity
                  })
                }
              }
            })
            Object.defineProperty(window.Sk, 'yieldLimit', {
              get: () => Infinity,
              set: () => {}
            })
          }
        })
      })()
      // xterm v5
      addStyle(
        new URL(
          'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/css/xterm.min.css'
        )
      )
      class Decoder {
        constructor() {
          this.bytesLeft = 0
          this.codePoint = 0
          this.lowerBound = 0
        }
        decode(data) {
          let tmp = ''
          for (let idx = 0; idx < data.length; idx++) {
            const code = data.charCodeAt(idx)
            if (0 === this.bytesLeft) {
              if (code <= 127) tmp += data.charAt(idx)
              else if (192 <= code && code <= 223) {
                this.codePoint = code - 192
                this.bytesLeft = 1
                this.lowerBound = 128
              } else if (224 <= code && code <= 239) {
                this.codePoint = code - 224
                this.bytesLeft = 2
                this.lowerBound = 2048
              } else if (240 <= code && code <= 247) {
                this.codePoint = code - 240
                this.bytesLeft = 3
                this.lowerBound = 65536
              } else if (248 <= code && code <= 251) {
                this.codePoint = code - 248
                this.bytesLeft = 4
                this.lowerBound = 2097152
              } else if (252 <= code && code <= 253) {
                this.codePoint = code - 252
                this.bytesLeft = 5
                this.lowerBound = 67108864
              } else tmp += '�'
            } else if (128 <= code && code <= 191) {
              this.bytesLeft--
              this.codePoint = (this.codePoint << 6) + (code - 128)
              if (this.bytesLeft === 0) {
                const charCode = this.codePoint
                if (
                  charCode < this.lowerBound ||
                  (55296 <= charCode && charCode <= 57343) ||
                  charCode > 1114111
                )
                  tmp += '�'
                else if (charCode < 65536) tmp += String.fromCharCode(charCode)
                else {
                  charCode -= 65536
                  tmp += String.fromCharCode(
                    55296 + ((charCode >>> 10) & 1023),
                    56320 + (1023 & charCode)
                  )
                }
              }
            } else {
              tmp += '�'
              this.bytesLeft = 0
              idx--
            }
          }
          return tmp
        }
      }
      const decoderInstance = new Decoder()
      class Xterm {
        constructor(
          elem,
          Terminal,
          WebglAddon,
          FitAddon,
          Unicode11Addon,
          CanvasAddon,
          WebLinksAddon
        ) {
          this.elem = elem
          this.term = new Terminal({
            fontSize: 15,
            fontFamily:
              '"Jetbrains Mono", "Fira Code", "Cascadia Code", "Noto Emoji", "Segoe UI Emoji", "Lucida Console", Menlo, courier-new, courier, monospace',
            theme: xterm_theme,
            cursorBlink: true,
            allowProposedApi: true,
            allowTransparency: true,
            cursorStyle: 'bar'
          })
          this.term.on = () => {}
          this.term.setOption = (name, value) => {
            this.term.options[name] = value
            this.fit()
          }
          this.fitAddon = new FitAddon()
          this.term.loadAddon(this.fitAddon)
          try {
            this.term.loadAddon(new WebglAddon())
          } catch {
            this.term.loadAddon(new CanvasAddon())
          }
          this.term.loadAddon(new Unicode11Addon())
          this.term.unicode.activeVersion = '11'
          this.term.loadAddon(new WebLinksAddon())
          this.term.onData(e => {
            if (window.WebpyInputCtrl && window.WebpyInputCtrl.runByWebPy) {
              const n =
                !!window.WebpyInputCtrl && window.WebpyInputCtrl.onData(e)
              if (n !== false) {
                if (n === true) this.write(e)
                else this.write(n)
              }
            }
          })
          this.term.open(elem)
          this.fit()
          window.addEventListener(
            'resize',
            (this.resizeListener = () => this.fit())
          )
          this.term.focus()
          this.term.blur()
          this.resizeHandler = null
          this.activate = true
          this.decoder = decoderInstance
        }
        fit() {
          this.fitAddon.fit()
        }
        info() {
          return {
            columns: this.term.cols,
            rows: this.term.rows
          }
        }
        output(e) {
          const n = this.decode(e)
          this.term.write(n)
          // var n = this.decoder.decode(e)
          // t(n) || this.term.write(n)
        }
        decode(data) {
          return this.decoder.decode(data)
        }
        outputNoEncode(e) {
          this.term.write(e)
        }
        write(e) {
          this.term.write(e)
        }
        writeln(e) {
          this.term.writeln(e)
        }
        showMessage(_message, _timeout) {
          // Unused
        }
        removeMessage() {
          // Unused
        }
        setWindowTitle(title) {
          document.title = title
        }
        setPreferences(_options) {
          // Unused
        }
        onInput(callback) {
          this.term.onData(ev => {
            if (this.activate) callback(ev)
          })
        }
        onResize(callback) {
          this.term.onResize(ev => {
            if (this.activate) callback(ev.cols, ev.rows)
          })
        }
        deactivate() {
          // if (this.resizeHandler) this.resizeHandler.dispose()
          this.activate = false
          this.term.blur()
        }
        reset() {
          this.term.clear()
          this.term.reset()
        }
        close() {
          window.removeEventListener('resize', this.resizeListener)
          this.term.dispose()
        }
      }
      let termInstance = null
      vueMixinManager.on('IdeEditor', instance => {
        const fnRunCode = instance.fnRunCode
        instance.fnRunCode = function (...args) {
          if (termInstance) termInstance.activate = true
          return fnRunCode.call(this, ...args)
        }
      })
      vueMixinManager.on('WsTermComp', instance => {
        const fnInitWS = instance.fnInitWS
        instance.fnInitWS = async function (api) {
          // 移植自 XesExt v2
          const xterm = document.getElementById('terminal')
          if (xterm && this.canInitWs) {
            if (!this.xterm) {
              xterm.style.backgroundColor =
                xterm.parentNode.style.backgroundColor = xterm_theme.background
              const { Terminal } = await import(
                'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'
              )
              const { WebglAddon } = await import(
                'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'
              )
              const { FitAddon } = await import(
                'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'
              )
              const { Unicode11Addon } = await import(
                'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'
              )
              const { CanvasAddon } = await import(
                'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'
              )
              const { WebLinksAddon } = await import(
                'https://cdn.jsdelivr.net/npm/@xterm/[email protected]/+esm'
              )
              termInstance = this.xterm = new Xterm(
                xterm,
                Terminal,
                WebglAddon,
                FitAddon,
                Unicode11Addon,
                CanvasAddon,
                WebLinksAddon
              )
              this.xterm.term.options.fontSize = Number(this.fontSize)
            }
            fnInitWS.call(this, api)
          }
        }
      })
      // monaco editor
      addStyle(
        new URL(
          'https://cdn.jsdelivr.net/npm/[email protected]/min/vs/editor/editor.main.css'
        )
      )
      addStyle(`
.editor, .ace-editor {
  padding: 0 !important;
  text-align: left !important;
}
`)
      window.MonacoEnvironment = {
        getWorkerUrl(fileName) {
          if (fileName === 'workerMain.js') {
            // fix SecurityError exception
            return `data:text/javascript;base64,${btoa(
              `(function(fetch){globalThis.fetch=function(url,...args){return fetch.call(this,'https://cdn.jsdelivr.net/npm/[email protected]/min/vs/base/worker/'+url,...args);};})(globalThis.fetch);importScripts('https://cdn.jsdelivr.net/npm/[email protected]/min/vs/base/worker/workerMain.js');`
            )}`
          }
        }
      }
      const languageMap = {
        c_cpp: 'cpp'
      }
      class DummyAceEditor {
        constructor(defaultLanguage) {
          this.value = ''
          this.language = defaultLanguage
        }
        setValue(value) {
          this.value = value
        }
        getSession() {
          return {
            setMode: mode => {
              const lang = mode.substring(9)
              this.language = languageMap[lang] ?? lang
            },
            setUndoManager: () => {}
          }
        }
      }
      vueMixinManager.on('AceEditor', instance => {
        instance.foldCode =
          instance.addCodeTips =
          instance.addCodeOfflineTips =
            () => {}
        instance.$options.mounted = [
          async function () {
            const lang = this.lang ?? 'c_cpp'
            const dummy = new DummyAceEditor(languageMap[lang] ?? lang)
            this.editor = new Proxy(dummy, {
              get(_, p) {
                if (Reflect.has(dummy, p)) return Reflect.get(dummy, p)
                return () => {}
              }
            })
            const Monaco = await import(
              'https://cdn.jsdelivr.net/npm/[email protected]/+esm'
            )
            this.editor = Monaco.editor.create(this.$el, {
              value: dummy.value,
              automaticLayout: true,
              language: dummy.language,
              fontSize: this.fontSize,
              readOnly: !!this.readOnly
            })
            const engine = new URL(
              plug.get_option_or('searchEngine', 'https://www.bing.com/?q=')
            )
            this.editor.addAction({
              id: 'aurora.search',
              label: `用 ${engine.host} 搜索选中内容`,
              contextMenuGroupId: 'aurora',
              run: () => {
                const a = document.createElement('a')
                a.target = '_blank'
                a.href = `${engine.toString()}${this.editor
                  .getModel()
                  .getValueInRange(this.editor.getSelection())}`
                a.click()
              }
            })
            this.editor.addAction({
              id: 'aurora.refactor',
              label: '用 AI 重构选中代码',
              contextMenuGroupId: 'aurora',
              run: async () => {
                if (window.ai && window.ai.generateText) {
                  if (this.editor.getRawOptions().readOnly) return
                  const originalTitle = document.title
                  document.title = 'Generating...'
                  // 最麻烦的一集
                  const WAITING_TEXT = '请稍等...'
                  let message = ''
                  let selectedRange = this.editor.getSelection()
                  const selectedText = this.editor
                    .getModel()
                    .getValueInRange(selectedRange)
                  this.editor.executeEdits('', [
                    {
                      range: selectedRange,
                      text: WAITING_TEXT
                    }
                  ])
                  selectedRange = this.editor.getSelection()
                  logger.log(
                    '您正在使用 AI 生成功能。请注意开发者不对 AI 生成结果的质量作保证,请遵循相关地区的法律法规。'
                  )
                  const [response] = await window.ai.generateText(
                    {
                      messages: [
                        {
                          role: 'user',
                          content: `Please refactor or implement the code or request described below or in comments. You should only emit the code as result, but you are able to add human-readable text using comments. The user language is Chinese. The output should match user's indentation.\n\n\`\`\`${this.editor
                            .getModel()
                            .getLanguageId()}\n${selectedText}\n\`\`\``
                        }
                      ]
                    },
                    {
                      onStreamResult: (resp, err) => {
                        if (err) logger.error('在 AI 生成时发生错误。', err)
                        else if (resp) {
                          const text = resp.message.content
                          message += text
                          if (message.startsWith('```')) {
                            const lines = message.split('\n')
                            lines.splice(0, 1)
                            message = lines.join('\n')
                          }
                          this.editor.executeEdits('', [
                            {
                              range: selectedRange,
                              text: message
                            }
                          ])
                          selectedRange = this.editor.getSelection()
                        }
                      }
                    }
                  )
                  const text = response.message.content
                  if (text.startsWith('```')) {
                    const lines = text.split('\n')
                    lines.splice(0, 1)
                    lines.pop()
                    this.editor.executeEdits('', [
                      {
                        range: selectedRange,
                        text: lines.join('\n')
                      }
                    ])
                  } else {
                    this.editor.executeEdits('', [
                      {
                        range: selectedRange,
                        text: response.message.content
                      }
                    ])
                  }
                  document.title = originalTitle // // https://github.com/alexanderatallah/window.ai/issues/83
                } else
                  alert(
                    'window.ai 不可用。请访问 https://github.com/alexanderatallah/window.ai 下载并配置浏览器扩展以使用 AI 扩展功能。'
                  )
              }
            })
            this.editor.getModel().setEOL(0)
            this.editor.onDidChangeModelContent(() => {
              const value = this.editor.getValue()
              this.$emit('listenEvent', {
                type: 'update',
                user_code: value
              })
              this.contentBackup = value
            })
            this.editor.setOptions =
              this.editor.setBehavioursEnabled =
              this.editor.setOptions =
              this.editor.resize =
                () => {}
            this.editor.gotoLine = line => {
              this.editor.revealLineInCenter(line)
            }
            this.editor.moveCursorTo = (lineNumber, column) =>
              this.editor.setPosition({
                lineNumber: Number(lineNumber) + 1,
                column: Number(column) + 1
              })
            this.editor.setReadOnly = readOnly =>
              this.editor.updateOptions({ readOnly })
            this.editor.setFontSize = fontSize =>
              this.editor.updateOptions({ fontSize })
            this.tryHandleExceptionForTerm = errors => {
              const SeverityMap = {
                error: 8,
                warning: 4,
                note: 1
              }
              if (!errors || errors.length === 0) {
                Monaco.editor.setModelMarkers(
                  this.editor.getModel(),
                  'error',
                  []
                )
                return
              }
              Monaco.editor.setModelMarkers(
                this.editor.getModel(),
                'error',
                errors.map(error => ({
                  startLineNumber: Number(error.Line),
                  endLineNumber: Number(error.Line),
                  message: error.Info.split('\r\n')[0].trim(),
                  startColumn: Number(error.Postion), // NOTE: This is a typo from Xueersi developer
                  endColumn: Infinity,
                  severity: SeverityMap[error.Type]
                }))
              )
            }
            this.editor.getSession = () => ({
              setMode: mode => {
                const lang = mode.substring(9)
                this.editor.getModel().setLanguage(languageMap[lang] ?? lang)
              },
              setUndoManager() {}
            })
            this.editor.onDidChangeCursorPosition(ev => {
              this.$emit('listenEvent', {
                type: 'cursorChange',
                user_code_cursor: {
                  row: ev.position.lineNumber - 1,
                  column: ev.position.column - 1
                }
              })
            })
            this.editor.onDidFocusEditorText(() => {
              this.$store.dispatch('editorFocus', true)
            })
            this.$store.dispatch('setEditor', this.editor)
          }
        ]
      })
    }
  })
  plug.plug('hidden', '访问不公开的作品。', () => {
    let loaded = false
    // Universal (Vue.js / React) patch
    webpackListener.push(exports => {
      if (
        typeof exports === 'function' &&
        exports.default === exports &&
        typeof exports.Axios === 'function' &&
        !loaded
      ) {
        loaded = true
        exports.interceptors.request.use(function (config) {
          if (config.url.startsWith('/compilers/v2/')) {
            config.url = '/community/v4/projects/detail'
            config.params = {
              id: config.params.id,
              lang: 'python'
            }
          } else if (config.url.startsWith('/api/projects/v2/')) {
            const code = config.url.split('/').at(-1)
            config.url = '/api/community/v4/projects/detail'
            config.params = {
              id: code,
              lang: 'scratch'
            }
          } else if (config.url.startsWith('/projects/v2/')) {
            const code = config.url.split('/').at(-1)
            config.url = '/community/v4/projects/detail'
            config.params = {
              id: code,
              lang: 'scratch'
            }
          }
          return config
        })
        exports.interceptors.response.use(function (resp) {
          if (resp.config.url === '/community/v4/projects/detail') {
            if (resp.data.stat === 1) {
              if (!resp.data.data.published)
                resp.data.data.published_at = resp.data.data.modified_at
            }
          }
          return resp
        })
      }
    })
  })
  plug.plug('dislike', '显示踩数。', () => {
    const UnlikeImg = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAv3pUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbDsMgDPvPKXYE8oCG49C1k3aDHX+BpFVZZwmT2MhNA/vn/YJHB6GA5EVLLSUZpEqlZoUmRxuMSQZ708LDWYfTIJPYbvZWS7w/dDwDjkBM+RKkzzDW2agS+foTRH5xn6jXWwTVCGJyAyOgxaSl6nL9hXVPM9QPdBKdx771i21vy/YdJtoZORkzqw/A/Qhws0IHC7ncjNn1COuL/LOnA/AFZY9Zb2AiYesAAAGEaUNDUElDQyBwcm9maWxlAAB4nH2RPUjDQBzFX9NKi1QcrCDiELA62UVFHKWKRbBQ2gqtOphc+gVNGpIUF0fBteDgx2LVwcVZVwdXQRD8AHF2cFJ0kRL/lxRaxHhw3I939x537wChWWWqGZgDVM0y0om4mMuvisFXhBBAGIMYlZipJzOLWXiOr3v4+HoX41ne5/4cfUrBZIBPJJ5jumERbxDPbFo6533iCCtLCvE58YRBFyR+5Lrs8hvnksMCz4wY2fQ8cYRYLHWx3MWsbKjE08RRRdUoX8i5rHDe4qxW66x9T/7CcEFbyXCd5ggSWEISKYiQUUcFVViI0aqRYiJN+3EP/7DjT5FLJlcFjBwLqEGF5PjB/+B3t2ZxatJNCseBnhfb/hgDgrtAq2Hb38e23ToB/M/Aldbx15rA7CfpjY4WPQL6t4GL644m7wGXO8DQky4ZkiP5aQrFIvB+Rt+UBwZugd41t7f2Pk4fgCx1tXwDHBwC4yXKXvd4d6i7t3/PtPv7AYV+cq40kTkPAAANdmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAtRXhpdjIiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6R0lNUD0iaHR0cDovL3d3dy5naW1wLm9yZy94bXAvIgogICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDplNDNjMDY2My03NTIxLTQyM2ItYjhmMC00NjYwYjRiNTI0NjQiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OTEwMmJjMDEtY2Y4NC00NDQwLThlYjktZmI1ZGI0ZDc0ZWVkIgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6NDE2YTU1MDUtODNhYS00MWJjLTliM2YtMTkxNzA3NmQxZTg1IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9ybT0iV2luZG93cyIKICAgR0lNUDpUaW1lU3RhbXA9IjE3MjI3NzQxODg2OTE4NzUiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zOCIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQ6MDg6MDRUMjA6MjM6MDgrMDg6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI0OjA4OjA0VDIwOjIzOjA4KzA4OjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YjAyYjQ3ZmMtYjUzNi00NDhlLTk0OGItYWZmMzc4ODRiMTY2IgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKFdpbmRvd3MpIgogICAgICBzdEV2dDp3aGVuPSIyMDI0LTA4LTA0VDIwOjIzOjA4Ii8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PhEGw8QAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfoCAQMFwiLkxoZAAACMklEQVRIx7WVP0+UQRjEf7PvwoHKH41G0URMVGoLrQwEYiGRwkJM/AT6OWysrP0UYiFCDBiQwkITtVBJLIyNlYIaUDzufHcsPM0FuTuOOyfZajc7+8w8z6wApmYXhekBrpI8btELiOawiT0NWpa4YTgMSFLRKT2IlUOdwLjta4g+INA8iiGEHsMZ24PAHgDbSdJwAFBKBZwuAL27JPkDCTr+vUOdYebhvEKg1+gEkLVAQkqp5l4s5lk0nAQK/EcEKWRCQ61W05AIO2IPAbH124Jrb2WhUJEu/teKUkoDQN8u5qZZ6TjVrmpk15YOON1m2by9dG6fP0a1KxI+0mIaVItXxyO0AqQ2yZawN4AS8LOyyohvEXEfc7TSeaGGj1kDT4rAGnglSC/BA4aDQLApChaixCOkNSfOirRfkpKRkKwUZY4bDdYgy4U+AtOIJzbvBZvA7apHGygJ4N7s446U3CFSkEQyCIHcDVy3ubRNFuaS3lq+g/VKULwyMZY37x4wNbPQA9yyObelMy1pFeum5ReTE2Objcyr29Y2qvxRWx9UwsyDX++EpCGRfhNEb0Mk/EzwY6ftGBuMRQLWMam6IyWVUtQ7ClneHiI7AZ9A1XOWDJ/JvTE5Ouydh2r9ksqg58BGVYaVZL9Rcrm59K6DQwf6y11dnUvAHLAKfBVaJoS7ZFmxPeFUwdzSU62tf98rcSzg7hjjh4S+XL44kreV6O9MzS6GjFz9+7rT6Mh5NxuCvwDVzNSdNFOkZAAAAABJRU5ErkJggg==`
    addStyle(`
.card-bottom-data-right-unlike {
  margin-right: 12px;
  vertical-align: middle;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}

.icon-cai:before {
  display: inline-block;
  content: "\\e61f";
  transform: rotate(180deg);
}

.recom-card .card-bottom-data-right-unlikes>i,.recom-card .card-bottom-data-right-views>i {
  margin-right: 5px
}

.recom-card .card-bottom-data-right-unlikes {
  margin-right: 12px
}

.recom-card .card-bottom-data-right-unlikes,.recom-card .card-bottom-data-right-views {
  vertical-align: middle;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap
}

.card-bottom-data-right-unlike:before {
  content: " ";
  width: 16px;
  height: 16px;
  background: url(${UnlikeImg}) no-repeat;
  background-size: 100% 100%;
  display: inline-block;
  margin-right: 5px;
  vertical-align: middle;
  margin-top: -2px
}

.work-card .work-detail .work-list .work-count .unlike_icon {
  margin-right: .5em
}

.work-card .work-detail .work-list .work-count .unlike_icon:before {
  background: url(${UnlikeImg})
}
`)
    vueMixinManager.on('109', instance => {
      if (instance.projectData) {
        const render = instance.$options.render
        instance.$options.render = function (renderProxy, createElement) {
          const res = render.call(this, renderProxy, createElement)
          const operateLeft =
            res.componentOptions.children[1].children[1].children[1].children[0]
              .children[1].children[0]
          const operateRight =
            res.componentOptions.children[1].children[1].children[1].children[0]
              .children[1].children[1]
          const dislike = operateLeft.children[2]
          /** @type {VElement} */
          const VElement = dislike.constructor
          if (
            typeof this.projectData.unlikes === 'number' &&
            this.projectData.unlikes > 0
          ) {
            dislike.children.push(
              new VElement(
                'span',
                undefined,
                [
                  new VElement(
                    undefined,
                    undefined,
                    undefined,
                    `${this.projectData.unlikes}`,
                    undefined,
                    this
                  )
                ],
                undefined,
                undefined,
                this
              )
            )
          }
          operateLeft.children.pop() // 删除“分享”
          operateRight.children.splice(0, 1) // 删除“公约”
          return res
        }
      }
    })
    vueMixinManager.on('WorkPiece', instance => {
      const render = instance.$options.render
      instance.$options.render = function (renderProxy, createElement) {
        const res = render.call(this, renderProxy, createElement)
        /** @type {VElement} */
        const VElement = res.constructor
        if (this.workDetail.removed) {
          return res
        }
        const workCount = res.children[0].children[1].children[1].children[1]
        workCount.children.splice(
          1,
          0,
          new VElement(
            'i',
            { staticClass: 'unlike_icon' },
            [
              new VElement(
                undefined,
                undefined,
                undefined,
                `${this.workDetail.unlikes}`,
                undefined,
                this
              )
            ],
            undefined,
            undefined,
            this
          )
        )
        return res
      }
    })
    vueMixinManager.on('Card', instance => {
      const render = instance.$options.render
      instance.$options.render = function (renderProxy, createElement) {
        const res = render.call(this, renderProxy, createElement)
        /** @type {VElement} */
        const VElement = res.constructor
        const rankDetail =
          res.children[0].children[0].children[6].children[1].children[1]
        rankDetail.children.splice(
          2,
          0,
          /** img */ new VElement(
            'img',
            {
              attrs: { width: '13', src: UnlikeImg },
              staticClass: 'rank-heart-img'
            },
            undefined,
            undefined,
            undefined,
            this
          ),
          new VElement(
            'span',
            { staticClass: 'rank-heart' },
            [
              new VElement(
                undefined,
                undefined,
                undefined,
                `${this.cell.unlikes}`,
                undefined,
                this
              )
            ],
            undefined,
            undefined,
            this
          )
        )
        return res
      }
    })
    vueMixinManager.on('NewCard', instance => {
      const render = instance.$options.render
      instance.$options.render = function (renderProxy, createElement) {
        const ret = render.call(this, renderProxy, createElement)
        /** @type {VElement} */
        const VElement = ret.constructor
        if (instance.$parent.$parent.list) {
          const work = instance.$parent.$parent.list.find(
            v => v && this.id === v.id
          )
          const cardBottomRight = ret.children[1].children[1].children[1]
          cardBottomRight.children.splice(
            1,
            0,
            new VElement(
              'div',
              { staticClass: 'card-bottom-data-right-unlike' },
              [
                new VElement(
                  undefined,
                  undefined,
                  undefined,
                  `${work.unlikes}`,
                  undefined,
                  this
                )
              ],
              undefined,
              undefined,
              this
            )
          )
        } else if (
          instance.$parent.$parent.$parent.$parent.$parent
            .keduoRecommendData?.[0]?.items
        ) {
          let work =
            instance.$parent.$parent.$parent.$parent.$parent.keduoRecommendData[0].items.find(
              v => this.id === v.id
            )
          if (work) {
            const cardBottomRight = ret.children[1].children[1].children[1]
            cardBottomRight.children.splice(
              1,
              0,
              new VElement(
                'div',
                { staticClass: 'card-bottom-data-right-unlikes' },
                [
                  new VElement(
                    'i',
                    { staticClass: 'iconfont icon-cai' },
                    undefined,
                    undefined,
                    undefined,
                    this
                  ),
                  new VElement(
                    undefined,
                    undefined,
                    undefined,
                    `${work.unlikes}`,
                    undefined,
                    this
                  )
                ],
                undefined,
                undefined,
                this
              )
            )
          } else {
            work = instance.$parent.$parent.$parent.$parent.$parent.tagWorkList
              .flatMap(v => v.items)
              .find(v => this.id === v.id)
            const cardBottomRight = ret.children[1].children[1].children[1]
            cardBottomRight.children.splice(
              1,
              0,
              new VElement(
                'div',
                { staticClass: 'card-bottom-data-right-unlike' },
                [
                  new VElement(
                    undefined,
                    undefined,
                    undefined,
                    `${work.unlikes}`,
                    undefined,
                    this
                  )
                ],
                undefined,
                undefined,
                this
              )
            )
          }
        } else if (instance.$parent.$parent.$parent.projects) {
          const work = instance.$parent.$parent.$parent.projects
            .flat()
            .find(v => this.id === v.id)
          const cardBottomRight = ret.children[1].children[1].children[1]
          cardBottomRight.children.splice(
            1,
            0,
            new VElement(
              'div',
              { staticClass: 'card-bottom-data-right-unlike' },
              [
                new VElement(
                  undefined,
                  undefined,
                  undefined,
                  `${work.unlikes}`,
                  undefined,
                  this
                )
              ],
              undefined,
              undefined,
              this
            )
          )
        }
        return ret
      }
    })
  })
  plug.done()
  if (window === window.top) {
    logger.clear()
    logger.log('Xueersi Aurora 已加载。')
    logger.log(
      '关于开发自己的功能,可以参见 %cAurora.help("development")%c。',
      CODEFONT_CSS,
      ''
    )
    window.Aurora.enable()
    logger.log('请使用 %cAurora.help()%c 查看帮助。', CODEFONT_CSS, '')
  }
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址