Greasy Fork is available in English.

Touhou.AI | Manga Translator

(WIP) Userscript for https://touhou.ai/imgtrans/, translate images on Pixiv, Twitter.

Устаревшая версия на 21.02.2022. Перейти к последней версии.

// ==UserScript==
// @name         Touhou.AI | Manga Translator
// @name:zh-CN   Touhou.AI | 图片翻译器
// @namespace    https://github.com/VoileLabs/imgtrans-userscript
// @version      0.6.0
// @description  (WIP) Userscript for https://touhou.ai/imgtrans/, translate images on Pixiv, Twitter.
// @description:zh-CN (WIP) https://touhou.ai/imgtrans/ 的用户脚本版本,一键翻译 Pixiv、Twitter 的图片
// @author       QiroNT
// @license      MIT
// @supportURL   https://github.com/VoileLabs/imgtrans-userscript/issues
// @require      https://unpkg.com/vue@3.2.31/dist/vue.runtime.global.prod.js
// @require      https://cdn.jsdelivr.net/gh/VoileLabs/imgtrans-userscript@52909c7d912cb34f1c905ae18c9e086363216c9c/wasm_bg.js
// @resource     wasm https://cdn.jsdelivr.net/gh/VoileLabs/imgtrans-userscript@52909c7d912cb34f1c905ae18c9e086363216c9c/wasm_bg.wasm
// @include      http*://www.pixiv.net/*
// @match        http://www.pixiv.net/
// @include      http*://twitter.com/*
// @match        http://twitter.com/
// @connect      i.pximg.net
// @connect      i-f.pximg.net
// @connect      i-cf.pximg.net
// @connect      pbs.twimg.com
// @connect      touhou.ai
// @grant        unsafeWindow
// @grant        GM.xmlHttpRequest
// @grant        GM_xmlhttpRequest
// @grant        GM.setValue
// @grant        GM_setValue
// @grant        GM.getValue
// @grant        GM_getValue
// @grant        GM.deleteValue
// @grant        GM_deleteValue
// @grant        GM.addValueChangeListener
// @grant        GM_addValueChangeListener
// @grant        GM.removeValueChangeListener
// @grant        GM_removeValueChangeListener
// @grant        GM.getResourceUrl
// @grant        GM_getResourceURL
// @run-at       document-end
// ==/UserScript==

/**
MIT License

Copyright (c) 2020-2022, VoileLabs

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

var GMP
{
  // polyfill functions
  const GMPFunctionMap = {
    setValue: GM_setValue,
    getValue: GM_getValue,
    deleteValue: GM_deleteValue,
    addValueChangeListener: GM_addValueChangeListener,
    removeValueChangeListener: GM_removeValueChangeListener,
    getResourceUrl: GM_getResourceURL,
  }
  const xmlHttpRequest = GM.xmlHttpRequest.bind(GM) || GM_xmlhttpRequest
  GMP = new Proxy(GM, {
    get(target, prop) {
      if (prop === 'xmlHttpRequest') {
        return (context) => {
          return new Promise((resolve, reject) => {
            xmlHttpRequest({
              ...context,
              onload(event) {
                context.onload?.()
                resolve(event)
              },
              onerror(event) {
                context.onerror?.()
                reject(event)
              },
            })
          })
        }
      }
      if (prop in target) {
        const v = target[prop]
        return typeof v === 'function' ? v.bind(target) : v
      }
      if (prop in GMPFunctionMap && typeof GMPFunctionMap[prop] === 'function') {
        return GMPFunctionMap[prop]
      }
    },
  })
}

(function (vue, wasmJsModule) {
  'use strict';

  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

  var wasmJsModule__default = /*#__PURE__*/_interopDefaultLegacy(wasmJsModule);

  const css = `
@keyframes imgtrans-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
`;
  const cssEl = document.createElement('style');
  cssEl.innerHTML = css;
  function checkCSS() {
      if (!document.head.contains(cssEl)) {
          document.head.appendChild(cssEl);
      }
  }

  function useGMStorage(key, initialValue) {
      const data = vue.ref(initialValue);
      async function read(newValue) {
          const rawValue = newValue !== null && newValue !== void 0 ? newValue : (await GMP.getValue(key));
          if (rawValue == null) {
              data.value = initialValue;
          }
          else {
              data.value = rawValue;
          }
      }
      read();
      let listener;
      if (GMP.addValueChangeListener)
          (async () => {
              listener = await GMP.addValueChangeListener(key, (name, oldValue, newValue, remote) => {
                  if (name === key)
                      read(newValue);
              });
          })();
      const stopWatch = vue.watch(data, async () => {
          if (data.value == null) {
              await GMP.deleteValue(key);
          }
          else {
              await GMP.setValue(key, data.value);
          }
      });
      vue.onScopeDispose(() => {
          stopWatch();
          if (GMP.removeValueChangeListener && listener)
              GMP.removeValueChangeListener(listener);
      });
      return data;
  }
  const detectionResolution = useGMStorage('detectionResolution', 'L');
  const textDetector = useGMStorage('textDetector', 'auto');
  const translator$1 = useGMStorage('translator', 'baidu');
  const renderTextDirection = useGMStorage('renderTextDirection', 'auto');
  const targetLang = useGMStorage('targetLang');
  const scriptLang = useGMStorage('scriptLanguage');

  var data$1 = { common:{ source:{ "download-image":"正在拉取原图",
        "download-image-error":"拉取原图出错" },
      client:{ submit:"正在提交翻译",
        "submit-error":"提交翻译出错",
        "download-image":"正在下载图片",
        "download-image-error":"下载图片出错" },
      status:{ "default":"未知状态",
        pending:"正在等待",
        pending_pos:"正在等待,列队还有 {pos} 张图片",
        detection:"正在检测文本",
        ocr:"正在识别文本",
        mask_generation:"正在生成文本掩码",
        inpainting:"正在修补图片",
        translating:"正在翻译文本",
        render:"正在渲染",
        error:"翻译出错",
        "error-lang":"不支持的语言" },
      control:{ translate:"翻译",
        batch:"翻译全部",
        reset:"还原" },
      batch:{ progress:"翻译中({count}/{total})",
        finish:"翻译完成",
        error:"翻译完成(有失败)" } },
    settings:{ title:"Touhou.AI | 图片翻译器设置",
      "detection-resolution":"文本扫描清晰度",
      "text-detector":"文本扫描器",
      "text-detector-options":{ auto:"默认" },
      translator:"翻译服务",
      "render-text-direction":"渲染字体方向",
      "render-text-direction-options":{ auto:"跟随原文本",
        horizontal:"仅限水平" },
      "target-language":"翻译语言",
      "target-language-options":{ auto:"跟随网页语言" },
      "script-language":"用户脚本语言",
      "script-language-options":{ auto:"跟随网页语言" },
      reset:"重置所有设置" } };
  data$1.common;
  data$1.settings;

  var data = { common:{ source:{ "download-image":"Downloading original image",
        "download-image-error":"Error during original image download" },
      client:{ submit:"Submitting translation",
        "submit-error":"Error during translation submission",
        "download-image":"Downloading translated image",
        "download-image-error":"Error during translated image download" },
      status:{ "default":"Unknown status",
        pending:"Pending",
        pending_pos:"Pending, {pos} in queue",
        detection:"Detecting text",
        ocr:"Scanning text",
        mask_generation:"Generating mask",
        inpainting:"Inpainting",
        translating:"Translating",
        render:"Rendering",
        error:"Error during translation",
        "error-lang":"Unsupported language" },
      control:{ translate:"Translate",
        batch:"Translate all",
        reset:"Reset" },
      batch:{ progress:"Translating ({count}/{total} finished)",
        finish:"Translation finished",
        error:"Translation finished with errors" } },
    settings:{ "detection-resolution":"Text detection resolution",
      "render-text-direction":"Render text direction",
      "render-text-direction-options":{ auto:"Follow original direction",
        horizontal:"Horizontal only" },
      reset:"Reset Settings",
      "target-language":"Translate target language",
      "target-language-options":{ auto:"Follow website language" },
      "text-detector":"Text detector",
      "text-detector-options":{ auto:"Default" },
      title:"Touhou.AI | Manga Translator Settings",
      translator:"Translator",
      "script-language":"Userscript language",
      "script-language-options":{ auto:"Follow website language" } } };
  data.common;
  data.settings;

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const messages = {
      'zh-CN': data$1,
      'en-US': data,
  };
  function tryMatchLang(lang) {
      if (lang.startsWith('zh'))
          return 'zh-CN';
      if (lang.startsWith('en'))
          return 'en-US';
      return 'zh-CN';
  }
  const realLang = vue.ref(navigator.language);
  const lang = vue.computed(() => scriptLang.value || tryMatchLang(realLang.value));
  vue.watch(lang, (o, n) => {
      if (o === n)
          return;
      console.log('lang changed: ' + lang.value, 'real: ' + realLang.value);
  });
  const t = (key, props = {}) => {
      return { key, props };
  };
  const tt = ({ key, props }) => {
      const msg = key.split('.').reduce((obj, k) => obj[k], messages[lang.value]) ||
          key.split('.').reduce((obj, k) => obj[k], messages['zh-CN']);
      if (!msg)
          return key;
      return msg.replace(/\{([^}]+)\}/g, (_, k) => {
          var _a;
          return (_a = String(props[k])) !== null && _a !== void 0 ? _a : '';
      });
  };
  let langEL;
  let langObserver;
  const changeLangEl = (el) => {
      if (langEL === el)
          return;
      if (langObserver)
          langObserver.disconnect();
      const observer = new MutationObserver((mutations) => {
          for (const mutation of mutations) {
              if (mutation.type === 'attributes' && mutation.attributeName === 'lang') {
                  const target = mutation.target;
                  if (target.lang) {
                      realLang.value = target.lang;
                  }
                  break;
              }
          }
      });
      observer.observe(el, { attributes: true });
      langObserver = observer;
      langEL = el;
      realLang.value = el.lang;
  };
  function BCP47ToISO639(code) {
      try {
          const lo = new Intl.Locale(code);
          switch (lo.language) {
              case 'zh': {
                  switch (lo.script) {
                      case 'Hans':
                          return 'CHS';
                      case 'Hant':
                          return 'CHT';
                  }
                  switch (lo.region) {
                      case 'CN':
                          return 'CHS';
                      case 'HK':
                      case 'TW':
                          return 'CHT';
                  }
                  return 'CHS';
              }
              case 'ja':
                  return 'JPN';
              case 'en':
                  return 'ENG';
              case 'ko':
                  return 'KOR';
              case 'vi':
                  return 'VIE';
              case 'cs':
                  return 'CSY';
              case 'nl':
                  return 'NLD';
              case 'fr':
                  return 'FRA';
              case 'de':
                  return 'DEU';
              case 'hu':
                  return 'HUN';
              case 'it':
                  return 'ITA';
              case 'pl':
                  return 'PLK';
              case 'pt':
                  return 'PTB';
              case 'ro':
                  return 'ROM';
              case 'ru':
                  return 'RUS';
              case 'es':
                  return 'ESP';
              case 'tr':
                  return 'TRK';
          }
          return 'CHS';
      }
      catch (e) {
          return 'CHS';
      }
  }

  async function submitTranslate(blob, suffix) {
      const formData = new FormData();
      formData.append('file', blob, 'image.' + suffix);
      formData.append('size', detectionResolution.value);
      formData.append('translator', translator$1.value);
      formData.append('tgt_lang', targetLang.value || BCP47ToISO639(realLang.value));
      formData.append('dir', renderTextDirection.value);
      formData.append('detector', textDetector.value);
      const result = await GMP.xmlHttpRequest({
          method: 'POST',
          url: 'https://touhou.ai/imgtrans/submit',
          // @ts-expect-error FormData is supported
          data: formData,
      });
      const json = JSON.parse(result.responseText);
      const id = json.task_id;
      return id;
  }
  async function getTranslateStatus(id) {
      const result = await GMP.xmlHttpRequest({
          method: 'GET',
          url: 'https://touhou.ai/imgtrans/task-state?taskid=' + id,
      });
      const data = JSON.parse(result.responseText);
      return {
          state: data.state,
          waiting: (data.waiting || 0),
      };
  }
  function getStatusText(status) {
      switch (status.state) {
          case 'pending':
              if (status.waiting > 0) {
                  return t('common.status.pending_pos', { pos: status.waiting });
              }
              else {
                  return t('common.status.pending');
              }
          case 'detection':
              return t('common.status.detection');
          case 'ocr':
              return t('common.status.ocr');
          case 'mask_generation':
              return t('common.status.mask_generation');
          case 'inpainting':
              return t('common.status.inpainting');
          case 'translating':
              return t('common.status.translating');
          case 'render':
              return t('common.status.render');
          case 'error':
              return t('common.status.error');
          case 'error-lang':
              return t('common.status.error-lang');
          default:
              return t('common.status.default');
      }
  }
  async function pullTransStatusUntilFinish(id, cb) {
      for (;;) {
          const timer = new Promise((resolve) => setTimeout(resolve, 500));
          const status = await getTranslateStatus(id);
          if (status.state === 'finished') {
              return;
          }
          else if (status.state === 'error') {
              throw t('common.status.error');
          }
          else if (status.state === 'error-lang') {
              throw t('common.status.error-lang');
          }
          else {
              cb(status);
          }
          await timer;
      }
  }
  function blobToImageData(blob) {
      const blobUrl = URL.createObjectURL(blob);
      return new Promise((resolve, reject) => {
          const img = new Image();
          img.onload = () => resolve(img);
          img.onerror = (err) => reject(err);
          img.src = blobUrl;
      }).then((img) => {
          URL.revokeObjectURL(blobUrl);
          const w = img.width;
          const h = img.height;
          const canvas = document.createElement('canvas');
          canvas.width = w;
          canvas.height = h;
          const ctx = canvas.getContext('2d');
          ctx.drawImage(img, 0, 0);
          return ctx.getImageData(0, 0, w, h);
      });
  }

  const _hoisted_1$1 = {
    width: "1.2em",
    height: "1.2em",
    preserveAspectRatio: "xMidYMid meet",
    viewBox: "0 0 32 32"
  };
  const _hoisted_2$1 = /*#__PURE__*/vue.createElementVNode("path", {
    fill: "currentColor",
    d: "M27.85 29H30l-6-15h-2.35l-6 15h2.15l1.6-4h6.85zm-7.65-6l2.62-6.56L25.45 23zM18 7V5h-7V2H9v3H2v2h10.74a14.71 14.71 0 0 1-3.19 6.18A13.5 13.5 0 0 1 7.26 9h-2.1a16.47 16.47 0 0 0 3 5.58A16.84 16.84 0 0 1 3 18l.75 1.86A18.47 18.47 0 0 0 9.53 16a16.92 16.92 0 0 0 5.76 3.84L16 18a14.48 14.48 0 0 1-5.12-3.37A17.64 17.64 0 0 0 14.8 7z"
  }, null, -1 /* HOISTED */);
  const _hoisted_3$1 = [
    _hoisted_2$1
  ];

  function render$1(_ctx, _cache) {
    return (vue.openBlock(), vue.createElementBlock("svg", _hoisted_1$1, _hoisted_3$1))
  }

  var IconCarbonTranslate = { name: 'carbon-translate', render: render$1 };
  /* vite-plugin-components disabled */

  const _hoisted_1 = {
    width: "1.2em",
    height: "1.2em",
    preserveAspectRatio: "xMidYMid meet",
    viewBox: "0 0 32 32"
  };
  const _hoisted_2 = /*#__PURE__*/vue.createElementVNode("path", {
    fill: "currentColor",
    d: "M18 28A12 12 0 1 0 6 16v6.2l-3.6-3.6L1 20l6 6l6-6l-1.4-1.4L8 22.2V16a10 10 0 1 1 10 10Z"
  }, null, -1 /* HOISTED */);
  const _hoisted_3 = [
    _hoisted_2
  ];

  function render(_ctx, _cache) {
    return (vue.openBlock(), vue.createElementBlock("svg", _hoisted_1, _hoisted_3))
  }

  var IconCarbonReset = { name: 'carbon-reset', render };
  /* vite-plugin-components disabled */

  let wasm;

  let cachegetUint8Memory0 = null;
  function getUint8Memory0() {
      if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
          cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
      }
      return cachegetUint8Memory0;
  }

  let WASM_VECTOR_LEN = 0;

  function passArray8ToWasm0(arg, malloc) {
      const ptr = malloc(arg.length * 1);
      getUint8Memory0().set(arg, ptr / 1);
      WASM_VECTOR_LEN = arg.length;
      return ptr;
  }

  function isLikeNone(x) {
      return x === undefined || x === null;
  }

  let cachegetInt32Memory0 = null;
  function getInt32Memory0() {
      if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
          cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
      }
      return cachegetInt32Memory0;
  }

  let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });

  cachedTextDecoder.decode();

  function getStringFromWasm0(ptr, len) {
      return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
  }
  /**
  * @param {Uint8Array} rgba
  * @param {number} width
  * @param {number} height
  * @param {number | undefined} hash_size
  * @returns {string}
  */
  function phash$1(rgba, width, height, hash_size) {
      try {
          const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
          var ptr0 = passArray8ToWasm0(rgba, wasm.__wbindgen_export_0);
          var len0 = WASM_VECTOR_LEN;
          wasm.phash(retptr, ptr0, len0, width, height, !isLikeNone(hash_size), isLikeNone(hash_size) ? 0 : hash_size);
          var r0 = getInt32Memory0()[retptr / 4 + 0];
          var r1 = getInt32Memory0()[retptr / 4 + 1];
          return getStringFromWasm0(r0, r1);
      } finally {
          wasm.__wbindgen_add_to_stack_pointer(16);
          wasm.__wbindgen_export_1(r0, r1);
      }
  }

  async function load(module, imports) {
      if (typeof Response === 'function' && module instanceof Response) {
          if (typeof WebAssembly.instantiateStreaming === 'function') {
              try {
                  return await WebAssembly.instantiateStreaming(module, imports);

              } catch (e) {
                  if (module.headers.get('Content-Type') != 'application/wasm') {
                      console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);

                  } else {
                      throw e;
                  }
              }
          }

          const bytes = await module.arrayBuffer();
          return await WebAssembly.instantiate(bytes, imports);

      } else {
          const instance = await WebAssembly.instantiate(module, imports);

          if (instance instanceof WebAssembly.Instance) {
              return { instance, module };

          } else {
              return instance;
          }
      }
  }

  async function init(input) {
      if (typeof input === 'undefined') {
          input = new URL('wasm_bg.js', (document.currentScript && document.currentScript.src || new URL('imgtrans-userscript.user.js', document.baseURI).href));
      }
      const imports = {};


      if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
          input = fetch(input);
      }



      const { instance, module } = await load(await input, imports);

      wasm = instance.exports;
      init.__wbindgen_wasm_module = module;

      return wasm;
  }


  function setWasm(w){wasm=w;}

  function phash(image) {
      return phash$1(new Uint8Array(image.data), image.width, image.height);
  }

  var pixiv = () => {
      const images = new Set();
      const instances = new Map();
      const translatedMap = new Map();
      const translateEnabledMap = new Map();
      function rescanImages() {
          const imageNodes = Array.from(document.querySelectorAll('img')).filter((node) => {
              var _a;
              return node.hasAttribute('srcset') ||
                  node.hasAttribute('data-trans') ||
                  ((_a = node.parentElement) === null || _a === void 0 ? void 0 : _a.classList.contains('sc-1pkrz0g-1'));
          });
          const removedImages = new Set(images);
          for (const node of imageNodes) {
              removedImages.delete(node);
              if (!images.has(node)) {
                  // new image
                  // console.log('new', node)
                  try {
                      instances.set(node, mountToNode(node));
                      images.add(node);
                  }
                  catch (e) {
                      // ignore
                  }
              }
          }
          for (const node of removedImages) {
              // removed image
              // console.log('remove', node)
              if (instances.has(node)) {
                  const instance = instances.get(node);
                  instance.stop();
                  instances.delete(node);
                  images.delete(node);
              }
          }
      }
      function mountToNode(imageNode) {
          // get current displayed image
          const src = imageNode.getAttribute('src');
          const srcset = imageNode.getAttribute('srcset');
          // get original image
          const parent = imageNode.parentElement;
          if (!parent)
              throw new Error('no parent');
          const originalSrc = parent.getAttribute('href') || src;
          const originalSrcSuffix = originalSrc.split('.').pop();
          // console.log(src, originalSrc)
          let originalImage;
          let translatedImage = translatedMap.get(originalSrc);
          let translateMounted = false;
          let buttonDisabled = false;
          const buttonProcessing = vue.ref(false);
          const buttonTranslated = vue.ref(false);
          const buttonText = vue.ref();
          const buttonHint = vue.ref('');
          // create a translate botton
          parent.style.position = 'relative';
          const container = document.createElement('div');
          parent.appendChild(container);
          const buttonApp = vue.createApp(vue.defineComponent({
              setup() {
                  const content = vue.computed(() => (buttonText.value ? tt(buttonText.value) : '') + buttonHint.value);
                  return () => 
                  // container
                  vue.h('div', {
                      style: {
                          position: 'absolute',
                          zIndex: '1',
                          bottom: '8px',
                          right: content.value ? '4px' : '26px',
                      },
                  }, [
                      vue.h('div', {
                          style: {
                              position: 'relative',
                          },
                      }, [
                          vue.h('div', {
                              style: {
                                  fontSize: '16px',
                                  lineHeight: '16px',
                                  height: '16px',
                                  padding: '3px',
                                  paddingLeft: content.value ? '28px' : '2px',
                                  border: '2px solid #D1D5DB',
                                  borderRadius: '6px',
                                  background: '#fff',
                              },
                          }, [content.value]),
                          vue.h('div', {
                              style: {
                                  position: 'absolute',
                                  left: '-5px',
                                  top: '-2px',
                                  background: '#fff',
                                  borderRadius: '24px',
                              },
                          }, [
                              // button
                              vue.h(buttonTranslated.value ? IconCarbonReset : IconCarbonTranslate, {
                                  style: {
                                      fontSize: '18px',
                                      lineHeight: '18px',
                                      width: '18px',
                                      height: '18px',
                                      padding: '6px',
                                      cursor: 'pointer',
                                  },
                                  onClick: vue.withModifiers(() => {
                                      toggle();
                                  }, ['stop', 'prevent']),
                              }),
                              vue.h('div', {
                                  style: {
                                      position: 'absolute',
                                      top: '0',
                                      left: '0',
                                      right: '0',
                                      bottom: '0',
                                      border: '2px solid #D1D5DB',
                                      ...(buttonProcessing.value
                                          ? {
                                              borderTop: '2px solid #7DD3FC',
                                              animation: 'imgtrans-spin 1s linear infinite',
                                          }
                                          : {}),
                                      borderRadius: '24px',
                                      pointerEvents: 'none',
                                  },
                              }),
                          ]),
                      ]),
                  ]);
              },
          }));
          buttonApp.mount(container);
          async function getTranslatedImage() {
              if (translatedImage)
                  return translatedImage;
              buttonDisabled = true;
              const text = buttonText.value;
              buttonHint.value = '';
              buttonProcessing.value = true;
              buttonText.value = t('common.source.download-image');
              if (!originalImage) {
                  // fetch original image
                  const result = await GMP.xmlHttpRequest({
                      method: 'GET',
                      responseType: 'blob',
                      url: originalSrc,
                      headers: { referer: 'https://www.pixiv.net/' },
                      overrideMimeType: 'text/plain; charset=x-user-defined',
                  }).catch((e) => {
                      buttonText.value = t('common.source.download-image-error');
                      throw e;
                  });
                  originalImage = result.response;
              }
              try {
                  const imageData = await blobToImageData(originalImage);
                  console.log('phash', phash(imageData));
              }
              catch (e) {
                  console.warn(e);
              }
              buttonText.value = t('common.client.submit');
              const id = await submitTranslate(originalImage, originalSrcSuffix).catch((e) => {
                  buttonText.value = t('common.client.submit-error');
                  throw e;
              });
              buttonText.value = t('common.status.pending');
              await pullTransStatusUntilFinish(id, (status) => {
                  buttonText.value = getStatusText(status);
              }).catch((e) => {
                  buttonText.value = e;
                  throw e;
              });
              buttonText.value = t('common.client.download-image');
              const image = await GMP.xmlHttpRequest({
                  method: 'GET',
                  responseType: 'blob',
                  url: 'https://touhou.ai/imgtrans/result/' + id + '/final.png',
              }).catch((e) => {
                  buttonText.value = t('common.client.download-image-error');
                  throw e;
              });
              const imageUri = URL.createObjectURL(image.response);
              translatedImage = imageUri;
              translatedMap.set(originalSrc, translatedImage);
              buttonText.value = text;
              buttonProcessing.value = false;
              buttonDisabled = false;
              return imageUri;
          }
          async function enable() {
              translateMounted = true;
              try {
                  const translated = await getTranslatedImage();
                  imageNode.setAttribute('data-trans', src);
                  imageNode.setAttribute('src', translated);
                  imageNode.removeAttribute('srcset');
                  buttonTranslated.value = true;
              }
              catch (e) {
                  buttonDisabled = false;
                  translateMounted = false;
                  throw e;
              }
          }
          function disable() {
              translateMounted = false;
              imageNode.setAttribute('src', src);
              if (srcset)
                  imageNode.setAttribute('srcset', srcset);
              imageNode.removeAttribute('data-trans');
              buttonTranslated.value = false;
          }
          // called on click
          function toggle() {
              if (buttonDisabled)
                  return;
              if (!translateMounted) {
                  translateEnabledMap.set(originalSrc, true);
                  enable();
              }
              else {
                  translateEnabledMap.delete(originalSrc);
                  disable();
              }
          }
          // enable if enabled
          if (translateEnabledMap.get(originalSrc))
              enable();
          return {
              imageNode,
              stop: () => {
                  buttonApp.unmount();
                  parent.removeChild(container);
                  if (translateMounted)
                      disable();
              },
              async enable() {
                  translateEnabledMap.set(originalSrc, true);
                  return await enable();
              },
              disable() {
                  translateEnabledMap.delete(originalSrc);
                  return disable();
              },
              isEnabled() {
                  return translateMounted;
              },
          };
      }
      // translate all
      let removeTransAll;
      function refreshTransAll() {
          if (document.querySelector('.sc-emr523-2'))
              return;
          const bookmark = document.querySelector('.gtm-main-bookmark');
          if (bookmark) {
              const parent = bookmark.parentElement.parentElement;
              if (parent.querySelector('[data-transall]'))
                  return;
              const container = document.createElement('div');
              parent.appendChild(container);
              const buttonApp = vue.createApp(vue.defineComponent({
                  setup() {
                      const started = vue.ref(false);
                      const total = vue.ref(0);
                      const finished = vue.ref(0);
                      const erred = vue.ref(false);
                      return () => vue.h('div', {
                          'data-transall': 'true',
                          style: {
                              display: 'inline-block',
                              marginRight: '13px',
                              padding: '0',
                              color: 'inherit',
                              height: '32px',
                              lineHeight: '32px',
                              cursor: 'pointer',
                              fontWeight: '700',
                          },
                          onClick: vue.withModifiers(() => {
                              if (started.value)
                                  return;
                              started.value = true;
                              total.value = instances.size;
                              const inc = () => {
                                  finished.value++;
                              };
                              const err = () => {
                                  erred.value = true;
                                  finished.value++;
                              };
                              for (const instance of instances.values()) {
                                  if (instance.isEnabled())
                                      inc();
                                  else
                                      instance.enable().then(inc).catch(err);
                              }
                          }, ['stop', 'prevent']),
                      }, [
                          tt(started.value
                              ? finished.value === total.value
                                  ? erred.value
                                      ? t('common.batch.error')
                                      : t('common.batch.finish')
                                  : t('common.batch.progress', {
                                      count: finished.value,
                                      total: total.value,
                                  })
                              : t('common.control.batch')),
                      ]);
                  },
              }));
              buttonApp.mount(container);
              removeTransAll = () => {
                  buttonApp.unmount();
                  parent.removeChild(container);
              };
          }
      }
      const imageObserver = new MutationObserver((mutations) => {
          rescanImages();
          refreshTransAll();
      });
      imageObserver.observe(document.body, { childList: true, subtree: true });
      return {
          stop() {
              instances.forEach((instance) => instance.stop());
              removeTransAll === null || removeTransAll === void 0 ? void 0 : removeTransAll();
          },
      };
  };

  function renderSettings(options) {
      const { itemOrientation = 'vertical', textStyle = {} } = options !== null && options !== void 0 ? options : {};
      return vue.h('div', {
          style: {
              display: 'flex',
              flexDirection: 'column',
              gap: '8px',
          },
      }, [
          // Detection resolution
          vue.h('div', {
              style: {
                  ...(itemOrientation === 'horizontal'
                      ? {
                          display: 'flex',
                          flexDirection: 'row',
                          alignItems: 'center',
                      }
                      : {}),
              },
          }, [
              vue.h('div', {
                  style: {
                      ...textStyle,
                  },
              }, tt(t('settings.detection-resolution'))),
              vue.h('select', {
                  value: detectionResolution.value,
                  onChange(e) {
                      detectionResolution.value = e.target.value;
                  },
              }, [
                  vue.h('option', { value: 'S' }, '1024px'),
                  vue.h('option', { value: 'M' }, '1536px'),
                  vue.h('option', { value: 'L' }, '2048px'),
                  vue.h('option', { value: 'X' }, '2560px'),
              ]),
          ]),
          // Text detector
          vue.h('div', {
              style: {
                  ...(itemOrientation === 'horizontal'
                      ? {
                          display: 'flex',
                          flexDirection: 'row',
                          alignItems: 'center',
                      }
                      : {}),
              },
          }, [
              vue.h('div', {
                  style: {
                      ...textStyle,
                  },
              }, tt(t('settings.text-detector'))),
              vue.h('select', {
                  value: textDetector.value,
                  onChange(e) {
                      textDetector.value = e.target.value;
                  },
              }, [
                  vue.h('option', { value: 'auto' }, tt(t('settings.text-detector-options.auto'))),
                  vue.h('option', { value: 'ctd' }, 'CTD'),
              ]),
          ]),
          // Translator
          vue.h('div', {
              style: {
                  ...(itemOrientation === 'horizontal'
                      ? {
                          display: 'flex',
                          flexDirection: 'row',
                          alignItems: 'center',
                      }
                      : {}),
              },
          }, [
              vue.h('div', {
                  style: {
                      ...textStyle,
                  },
              }, tt(t('settings.translator'))),
              vue.h('select', {
                  value: translator$1.value,
                  onChange(e) {
                      translator$1.value = e.target.value;
                  },
              }, [
                  vue.h('option', { value: 'baidu' }, 'Baidu'),
                  vue.h('option', { value: 'google' }, 'Google'),
                  vue.h('option', { value: 'deepl' }, 'DeepL'),
              ]),
          ]),
          // Render text direction
          vue.h('div', {
              style: {
                  ...(itemOrientation === 'horizontal'
                      ? {
                          display: 'flex',
                          flexDirection: 'row',
                          alignItems: 'center',
                      }
                      : {}),
              },
          }, [
              vue.h('div', {
                  style: {
                      ...textStyle,
                  },
              }, tt(t('settings.render-text-direction'))),
              vue.h('select', {
                  value: renderTextDirection.value,
                  onChange(e) {
                      renderTextDirection.value = e.target.value;
                  },
              }, [
                  vue.h('option', { value: 'auto' }, tt(t('settings.render-text-direction-options.auto'))),
                  vue.h('option', { value: 'horizontal' }, tt(t('settings.render-text-direction-options.horizontal'))),
              ]),
          ]),
          // Target language
          vue.h('div', {
              style: {
                  ...(itemOrientation === 'horizontal'
                      ? {
                          display: 'flex',
                          flexDirection: 'row',
                          alignItems: 'center',
                      }
                      : {}),
              },
          }, [
              vue.h('div', {
                  style: {
                      ...textStyle,
                  },
              }, tt(t('settings.target-language'))),
              vue.h('select', {
                  value: targetLang.value,
                  onChange(e) {
                      targetLang.value = e.target.value;
                  },
              }, [
                  vue.h('option', { value: '' }, tt(t('settings.target-language-options.auto'))),
                  ...[
                      ['CHS', '简体中文'],
                      ['CHT', '繁體中文'],
                      ['JPN', '日本語'],
                      ['ENG', 'English'],
                      ['KOR', '한국어'],
                      ['VIN', 'Tiếng Việt'],
                      ['CSY', 'čeština'],
                      ['NLD', 'Nederlands'],
                      ['FRA', 'français'],
                      ['DEU', 'Deutsch'],
                      ['HUN', 'magyar nyelv'],
                      ['ITA', 'italiano'],
                      ['PLK', 'polski'],
                      ['PTB', 'português'],
                      ['ROM', 'limba română'],
                      ['RUS', 'русский язык'],
                      ['ESP', 'español'],
                      ['TRK', 'Türk dili'],
                  ].map(([value, text]) => vue.h('option', { value }, text)),
              ]),
          ]),
          // Script language
          vue.h('div', {
              style: {
                  ...(itemOrientation === 'horizontal'
                      ? {
                          display: 'flex',
                          flexDirection: 'row',
                          alignItems: 'center',
                      }
                      : {}),
              },
          }, [
              vue.h('div', {
                  style: {
                      ...textStyle,
                  },
              }, tt(t('settings.script-language'))),
              vue.h('select', {
                  value: scriptLang.value,
                  onChange(e) {
                      scriptLang.value = e.target.value;
                  },
              }, [
                  vue.h('option', { value: '' }, tt(t('settings.script-language-options.auto'))),
                  vue.h('option', { value: 'zh-CN' }, '简体中文'),
                  vue.h('option', { value: 'en-US' }, 'English'),
              ]),
          ]),
          // Reset
          vue.h('div', [
              vue.h('button', {
                  onClick: vue.withModifiers(() => {
                      detectionResolution.value = null;
                      textDetector.value = null;
                      translator$1.value = null;
                      renderTextDirection.value = null;
                      targetLang.value = null;
                      scriptLang.value = null;
                  }, ['stop', 'prevent']),
              }, tt(t('settings.reset'))),
          ]),
      ]);
  }

  var pixivSettings = () => {
      const wrapper = document.getElementById('wrapper');
      if (!wrapper)
          return {};
      const adFooter = wrapper.querySelector('.ad-footer');
      if (!adFooter)
          return {};
      const settingsContainer = document.createElement('div');
      const settingsApp = vue.createApp(vue.defineComponent({
          setup() {
              return () => vue.h('div', {
                  style: {
                      paddingTop: '10px',
                      paddingLeft: '20px',
                      paddingRight: '20px',
                      paddingBottom: '15px',
                      marginBottom: '10px',
                      background: '#fff',
                      border: '1px solid #d6dee5',
                  },
              }, [
                  vue.h('h2', {
                      style: {
                          fontSize: '18px',
                          fontWeight: 'bold',
                      },
                  }, tt(t('settings.title'))),
                  vue.h('div', {
                      style: {
                          width: '665px',
                          margin: '10px auto',
                      },
                  }, renderSettings({
                      itemOrientation: 'horizontal',
                      textStyle: {
                          width: '185px',
                          fontWeight: 'bold',
                      },
                  })),
              ]);
          },
      }));
      settingsApp.mount(settingsContainer);
      wrapper.insertBefore(settingsContainer, adFooter);
      return {
          stop() {
              settingsApp.unmount();
              settingsContainer.remove();
          },
      };
  };

  var twitter = () => {
      var _a;
      const statusId = (_a = location.pathname.match(/\/status\/(\d+)/)) === null || _a === void 0 ? void 0 : _a[1];
      const translatedMap = vue.reactive({});
      const translateStatusMap = vue.shallowReactive({});
      const translateEnabledMap = vue.reactive({});
      const originalImageMap = {};
      let initObserver;
      let layersObserver;
      let layers = document.getElementById('layers');
      let dialog;
      const createDialogInstance = () => {
          const active = vue.ref(0);
          const updateRef = vue.ref();
          const buttonParent = dialog.querySelector('[aria-labelledby="modal-header"][role="dialog"]').firstChild
              .firstChild;
          const images = vue.computed(() => {
              updateRef.value;
              return Array.from(buttonParent.firstChild.querySelectorAll('img'));
          });
          const currentImg = vue.computed(() => {
              const img = images.value[active.value];
              if (!img)
                  return undefined;
              return img.getAttribute('data-transurl') || img.src;
          });
          const stopImageWatch = vue.watch([images, translateEnabledMap, translatedMap], () => {
              for (const img of images.value) {
                  const div = img.previousSibling;
                  if (img.hasAttribute('data-transurl')) {
                      const transurl = img.getAttribute('data-transurl');
                      if (!translateEnabledMap[transurl]) {
                          if (div)
                              div.style.backgroundImage = `url("${transurl}")`;
                          img.src = transurl;
                          img.removeAttribute('data-transurl');
                      }
                  }
                  else if (translateEnabledMap[img.src] && translatedMap[img.src]) {
                      const ori = img.src;
                      img.setAttribute('data-transurl', ori);
                      img.src = translatedMap[ori];
                      if (div)
                          div.style.backgroundImage = `url("${translatedMap[ori]}")`;
                  }
              }
          });
          const getTranslatedImage = async (url) => {
              if (translatedMap[url])
                  return translatedMap[url];
              translateStatusMap[url] = vue.computed(() => tt(t('common.source.download-image')));
              if (!originalImageMap[url]) {
                  // fetch original image
                  const result = await GMP.xmlHttpRequest({
                      method: 'GET',
                      responseType: 'blob',
                      url,
                      headers: { referer: 'https://twitter.com/' },
                      overrideMimeType: 'text/plain; charset=x-user-defined',
                  }).catch((e) => {
                      translateStatusMap[url] = vue.computed(() => tt(t('common.source.download-image-error')));
                      throw e;
                  });
                  originalImageMap[url] = result.response;
              }
              const originalImage = originalImageMap[url];
              try {
                  const imageData = await blobToImageData(originalImage);
                  console.log('phash', phash(imageData));
              }
              catch (e) {
                  console.warn(e);
              }
              translateStatusMap[url] = vue.computed(() => tt(t('common.client.submit')));
              const originalSrcSuffix = url.split('.').pop();
              const id = await submitTranslate(originalImage, originalSrcSuffix).catch((e) => {
                  translateStatusMap[url] = vue.computed(() => tt(t('common.client.submit-error')));
                  throw e;
              });
              translateStatusMap[url] = vue.computed(() => tt(t('common.status.pending')));
              await pullTransStatusUntilFinish(id, (status) => {
                  translateStatusMap[url] = vue.computed(() => tt(getStatusText(status)));
              }).catch((e) => {
                  translateStatusMap[url] = vue.computed(() => tt(e));
                  throw e;
              });
              translateStatusMap[url] = vue.computed(() => tt(t('common.client.download-image')));
              const image = await GMP.xmlHttpRequest({
                  method: 'GET',
                  responseType: 'blob',
                  url: 'https://touhou.ai/imgtrans/result/' + id + '/final.png',
              }).catch((e) => {
                  translateStatusMap[url] = vue.computed(() => tt(t('common.client.download-image-error')));
                  throw e;
              });
              const imageUri = URL.createObjectURL(image.response);
              translatedMap[url] = imageUri;
              // https://github.com/vuejs/core/blob/1574edd490bd5cc0a213bc9f48ff41a1dc43ab22/packages/reactivity/src/baseHandlers.ts#L153
              translateStatusMap[url] = vue.computed(() => undefined);
              return imageUri;
          };
          const enable = async (url) => {
              await getTranslatedImage(url);
              translateEnabledMap[url] = true;
          };
          const disable = (url) => {
              translateEnabledMap[url] = false;
          };
          const referenceEl = buttonParent.children[2];
          const container = referenceEl.cloneNode(true);
          container.style.top = '48px';
          // container.style.display = 'flex'
          const stopDisplayWatch = vue.watchEffect(() => {
              container.style.display = currentImg.value ? 'flex' : 'none';
          });
          container.style.flexDirection = 'row';
          container.style.flexWrap = 'nowrap';
          const child = container.firstChild;
          const referenceChild = referenceEl.firstChild;
          const backgroundColor = vue.ref(referenceChild.style.backgroundColor);
          buttonParent.appendChild(container);
          const buttonProcessing = vue.computed(() => { var _a; return currentImg.value && !!((_a = translateStatusMap[currentImg.value]) === null || _a === void 0 ? void 0 : _a.value); });
          const buttonTranslated = vue.computed(() => currentImg.value && !!translateEnabledMap[currentImg.value]);
          const buttonContent = vue.computed(() => { var _a; return (currentImg.value ? (_a = translateStatusMap[currentImg.value]) === null || _a === void 0 ? void 0 : _a.value : ''); });
          container.onclick = vue.withModifiers(() => {
              var _a;
              if (!currentImg.value)
                  return;
              if ((_a = translateStatusMap[currentImg.value]) === null || _a === void 0 ? void 0 : _a.value)
                  return;
              if (translateEnabledMap[currentImg.value]) {
                  disable(currentImg.value);
              }
              else {
                  enable(currentImg.value);
              }
          }, ['stop', 'prevent']);
          const spinnerContainer = container.firstChild;
          const processingSpinner = document.createElement('div');
          processingSpinner.style.position = 'absolute';
          processingSpinner.style.top = '0';
          processingSpinner.style.left = '0';
          processingSpinner.style.bottom = '0';
          processingSpinner.style.right = '0';
          processingSpinner.style.borderTop = '1px solid #A1A1AA';
          processingSpinner.style.animation = 'imgtrans-spin 1s linear infinite';
          processingSpinner.style.borderRadius = '9999px';
          const stopSpinnerWatch = vue.watch(buttonProcessing, (p, o) => {
              if (p === o)
                  return;
              if (p && !spinnerContainer.contains(processingSpinner))
                  spinnerContainer.appendChild(processingSpinner);
              else if (spinnerContainer.contains(processingSpinner))
                  spinnerContainer.removeChild(processingSpinner);
          }, { immediate: true });
          const svg = container.querySelector('svg');
          const svgParent = svg.parentElement;
          const buttonIconContainer = document.createElement('div');
          svgParent.insertBefore(buttonIconContainer, svg);
          svgParent.removeChild(svg);
          const buttonIconApp = vue.createApp(vue.defineComponent({
              setup() {
                  return () => vue.h(buttonTranslated.value ? IconCarbonReset : IconCarbonTranslate, {
                      style: {
                          width: '20px',
                          height: '20px',
                          marginTop: '4px',
                      },
                  });
              },
          }));
          buttonIconApp.mount(buttonIconContainer);
          const buttonStatusContainer = document.createElement('div');
          container.insertBefore(buttonStatusContainer, container.firstChild);
          const buttonStatusApp = vue.createApp(vue.defineComponent({
              setup() {
                  return () => vue.h('div', {
                      style: {
                          display: buttonContent.value ? '' : 'none',
                          marginRight: '-12px',
                          padding: '2px',
                          paddingLeft: '4px',
                          paddingRight: '16px',
                          color: '#fff',
                          backgroundColor: backgroundColor.value,
                          borderRadius: '4px',
                      },
                  }, [buttonContent.value]);
              },
          }));
          buttonStatusApp.mount(buttonStatusContainer);
          return {
              active,
              update() {
                  vue.triggerRef(updateRef);
                  if (referenceChild.style.backgroundColor)
                      child.style.backgroundColor = backgroundColor.value = referenceChild.style.backgroundColor;
              },
              stop() {
                  stopDisplayWatch();
                  stopSpinnerWatch();
                  stopImageWatch();
                  buttonIconApp.unmount();
                  buttonStatusApp.unmount();
                  buttonParent.removeChild(container);
                  for (const img of images.value) {
                      if (img.hasAttribute('data-transurl')) {
                          const transurl = img.getAttribute('data-transurl');
                          img.src = transurl;
                          img.removeAttribute('data-transurl');
                      }
                  }
              },
          };
      };
      let dialogInstance;
      const rescanLayers = () => {
          var _a;
          const [newDialog] = Array.from(layers.children).filter((el) => { var _a, _b, _c; return (_c = (_b = (_a = el.querySelector('[aria-labelledby="modal-header"][role="dialog"]')) === null || _a === void 0 ? void 0 : _a.firstChild) === null || _b === void 0 ? void 0 : _b.firstChild) === null || _c === void 0 ? void 0 : _c.childNodes[2]; });
          if (newDialog !== dialog || !newDialog) {
              dialogInstance === null || dialogInstance === void 0 ? void 0 : dialogInstance.stop();
              dialogInstance = undefined;
              dialog = newDialog;
              if (!dialog)
                  return;
              dialogInstance = createDialogInstance();
          }
          const newIndex = Number((_a = location.pathname.match(/\/status\/\d+\/photo\/(\d+)/)) === null || _a === void 0 ? void 0 : _a[1]) - 1;
          if (newIndex !== dialogInstance.active.value) {
              dialogInstance.active.value = newIndex;
          }
          dialogInstance.update();
      };
      const onLayersUpdate = () => {
          rescanLayers();
          layersObserver = new MutationObserver((mutations) => {
              rescanLayers();
          });
          layersObserver.observe(layers, { childList: true, subtree: true });
      };
      if (layers)
          onLayersUpdate();
      else {
          initObserver = new MutationObserver((mutations) => {
              layers = document.getElementById('layers');
              if (layers) {
                  onLayersUpdate();
                  initObserver === null || initObserver === void 0 ? void 0 : initObserver.disconnect();
              }
          });
          initObserver.observe(document.body, { childList: true, subtree: true });
      }
      return {
          canKeep(url) {
              var _a;
              const urlStatusId = (_a = url.match(/\/status\/(\d+)/)) === null || _a === void 0 ? void 0 : _a[1];
              return urlStatusId === statusId;
          },
          stop() {
              layersObserver === null || layersObserver === void 0 ? void 0 : layersObserver.disconnect();
              initObserver === null || initObserver === void 0 ? void 0 : initObserver.disconnect();
          },
      };
  };

  var twitterSettings = () => {
      let settingsTab;
      let textApp;
      const checkTab = () => {
          const tablist = document.querySelector('[role="tablist"]') || document.querySelector('[data-testid="loggedOutPrivacySection"]');
          if (!tablist) {
              if (textApp) {
                  textApp.unmount();
                  textApp = undefined;
              }
              return;
          }
          if (tablist.querySelector('div[data-imgtrans-settings]'))
              return;
          const inactiveRefrenceEl = Array.from(tablist.children).find((el) => el.children.length < 2 && el.querySelector('a'));
          if (!inactiveRefrenceEl)
              return;
          settingsTab = inactiveRefrenceEl.cloneNode(true);
          settingsTab.setAttribute('data-imgtrans-settings', 'true');
          const textEl = settingsTab.querySelector('span');
          if (textEl) {
              textApp = vue.createApp(vue.defineComponent({
                  render() {
                      return tt(t('settings.title'));
                  },
              }));
              textApp.mount(textEl);
          }
          const linkEl = settingsTab.querySelector('a');
          if (linkEl)
              linkEl.href = '/settings/__imgtrans';
          tablist.appendChild(settingsTab);
      };
      let settingsApp;
      const checkSettings = () => {
          var _a, _b;
          const section = (_b = (_a = document.querySelector('[data-testid="error-detail"]')) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.parentElement;
          if (!(section === null || section === void 0 ? void 0 : section.querySelector('[data-imgtrans-settings-section]'))) {
              if (settingsApp) {
                  settingsApp.unmount();
                  settingsApp = undefined;
              }
              if (!section)
                  return;
          }
          const title = tt(t('settings.title')) + ' / Twitter';
          if (document.title !== title)
              document.title = title;
          if (settingsApp)
              return;
          const errorPage = section.firstChild;
          errorPage.style.display = 'none';
          const settingsContainer = document.createElement('div');
          settingsContainer.setAttribute('data-imgtrans-settings-section', 'true');
          section.appendChild(settingsContainer);
          settingsApp = vue.createApp(vue.defineComponent({
              setup() {
                  vue.onUnmounted(() => {
                      errorPage.style.display = '';
                  });
                  return () => 
                  // container
                  vue.h('div', {
                      style: {
                          paddingLeft: '16px',
                          paddingRight: '16px',
                      },
                  }, [
                      // title
                      vue.h('div', {
                          style: {
                              display: 'flex',
                              height: '53px',
                              alignItems: 'center',
                          },
                      }, vue.h('h2', {
                          style: {
                              fontSize: '20px',
                              lineHeight: '24px',
                          },
                      }, tt(t('settings.title')))),
                      renderSettings(),
                  ]);
              },
          }));
          settingsApp.mount(settingsContainer);
      };
      const listObserver = new MutationObserver(() => {
          checkTab();
          if (location.pathname.match(/\/settings\/__imgtrans/)) {
              if (settingsTab && settingsTab.children.length < 2) {
                  settingsTab.style.backgroundColor = '#F7F9F9';
                  const activeIndicator = document.createElement('div');
                  activeIndicator.style.position = 'absolute';
                  activeIndicator.style.zIndex = '1';
                  activeIndicator.style.top = '0';
                  activeIndicator.style.left = '0';
                  activeIndicator.style.bottom = '0';
                  activeIndicator.style.right = '0';
                  activeIndicator.style.borderRight = '2px solid #1D9Bf0';
                  activeIndicator.style.pointerEvents = 'none';
                  settingsTab.appendChild(activeIndicator);
              }
              checkSettings();
          }
          else {
              if (settingsTab && settingsTab.children.length > 1) {
                  settingsTab.style.backgroundColor = '';
                  settingsTab.removeChild(settingsTab.lastChild);
              }
              if (settingsApp) {
                  settingsApp.unmount();
                  settingsApp = undefined;
              }
          }
      });
      listObserver.observe(document.body, { childList: true, subtree: true });
      return {
          canKeep(url) {
              return url.includes('twitter.com') && url.includes('settings/');
          },
          stop() {
              settingsApp === null || settingsApp === void 0 ? void 0 : settingsApp.unmount();
              listObserver.disconnect();
          },
      };
  };

  function createScopedInstance(cb) {
      const scope = vue.effectScope();
      const i = scope.run(cb);
      scope.run(() => {
          vue.onScopeDispose(() => {
              var _a;
              (_a = i.stop) === null || _a === void 0 ? void 0 : _a.call(i);
          });
      });
      return { scope, i };
  }
  async function initWasm() {
      const uri = await GMP.getResourceUrl('wasm');
      try {
          if (/^data:.+;base64,/.test(uri)) {
              const data = window.atob(uri.split(';base64,', 2)[1]);
              const buffer = new Uint8Array(data.length);
              for (let i = 0; i < data.length; i++) {
                  buffer[i] = data.charCodeAt(i);
              }
              await init(buffer);
          }
          else {
              await init(uri);
          }
      }
      catch (e) {
          setWasm(wasmJsModule__default["default"]);
      }
  }
  Promise.allSettled =
      Promise.allSettled ||
          ((promises) => Promise.all(promises.map((p) => p
              .then((value) => ({
              status: 'fulfilled',
              value,
          }))
              .catch((reason) => ({
              status: 'rejected',
              reason,
          })))));
  let currentURL;
  let translator;
  let settingsInjector;
  const onUpdate = () => {
      var _a, _b, _c, _d;
      if (currentURL !== location.href) {
          currentURL = location.href;
          // there is a navigation in the page
          /* ensure css is loaded */
          checkCSS();
          /* update i18n element */
          changeLangEl(document.documentElement);
          /* update translator */
          // only if the translator needs to be updated
          if (!((_b = translator === null || translator === void 0 ? void 0 : (_a = translator.i).canKeep) === null || _b === void 0 ? void 0 : _b.call(_a, currentURL))) {
              // unmount previous translator
              translator === null || translator === void 0 ? void 0 : translator.scope.stop();
              translator = undefined;
              // check if the page is a image page
              const url = new URL(location.href);
              // https://www.pixiv.net/(en/)artworks/<id>
              if (url.hostname.endsWith('pixiv.net') && url.pathname.match(/\/artworks\//)) {
                  translator = createScopedInstance(pixiv);
              }
              // https://twitter.com/<user>/status/<id>
              else if (url.hostname.endsWith('twitter.com') && url.pathname.match(/\/status\//)) {
                  translator = createScopedInstance(twitter);
              }
          }
          /* update settings page */
          if (!((_d = settingsInjector === null || settingsInjector === void 0 ? void 0 : (_c = settingsInjector.i).canKeep) === null || _d === void 0 ? void 0 : _d.call(_c, currentURL))) {
              // unmount previous settings injector
              settingsInjector === null || settingsInjector === void 0 ? void 0 : settingsInjector.scope.stop();
              settingsInjector = undefined;
              // check if the page is a settings page
              const url = new URL(location.href);
              // https://www.pixiv.net/setting_user.php
              if (url.hostname.endsWith('pixiv.net') && url.pathname.match(/\/setting_user\.php/)) {
                  settingsInjector = createScopedInstance(pixivSettings);
              }
              // https://twitter.com/settings/<tab>
              if (url.hostname.endsWith('twitter.com') && url.pathname.match(/\/settings\//)) {
                  settingsInjector = createScopedInstance(twitterSettings);
              }
          }
      }
  };
  Promise.allSettled([initWasm()]).then((results) => {
      for (const result of results) {
          if (result.status === 'rejected')
              console.warn(result.reason);
      }
      const installObserver = new MutationObserver(onUpdate);
      installObserver.observe(document.body, { childList: true, subtree: true });
      onUpdate();
  });

})(Vue, wasmJsModule);