// ==UserScript==
// @name Cotrans Manga/Image Translator (Regular Edition)
// @name:zh-CN Cotrans 漫画/图片翻译器 (常规版)
// @namespace https://cotrans.touhou.ai/userscript/#regular
// @version 0.8.0-bata.7
// @description (WIP) Translate texts in images on Pixiv, Twitter
// @description:zh-CN (WIP) 一键翻译图片内文字,支持 Pixiv、Twitter
// @author QiroNT
// @license GPL-3.0
// @contributionURL https://ko-fi.com/voilelabs
// @supportURL https://discord.gg/975FRV8ca6
// @source https://cotrans.touhou.ai/
// @include http*://www.pixiv.net/*
// @match http://www.pixiv.net/
// @include http*://twitter.com/*
// @match http://twitter.com/
// @connect pixiv.net
// @connect pximg.net
// @connect twitter.com
// @connect twimg.com
// @connect api.cotrans.touhou.ai
// @connect cotrans-r2.moe.ci
// @connect *
// @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 window.onurlchange
// @run-at document-idle
// @downloadURL none
// ==/UserScript==
/* eslint-disable no-undef, unused-imports/no-unused-vars */
const VERSION = '0.8.0-bata.7'
const EDITION = 'regular'
let GMP
{
// polyfill functions
const GMPFunctionMap = {
xmlHttpRequest: typeof GM_xmlhttpRequest !== 'undefined' ? GM_xmlhttpRequest : undefined,
setValue: typeof GM_setValue !== 'undefined' ? GM_setValue : undefined,
getValue: typeof GM_getValue !== 'undefined' ? GM_getValue : undefined,
deleteValue: typeof GM_deleteValue !== 'undefined' ? GM_deleteValue : undefined,
addValueChangeListener: typeof GM_addValueChangeListener !== 'undefined' ? GM_addValueChangeListener : undefined,
removeValueChangeListener: typeof GM_removeValueChangeListener !== 'undefined' ? GM_removeValueChangeListener : undefined,
}
const xmlHttpRequest = GM.xmlHttpRequest.bind(GM) || GMPFunctionMap.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]
console.error(`[Cotrans Manga Translator] GM.${prop} isn't supported in your userscript engine and it's required by this script. This may lead to unexpected behavior.`)
},
})
}
(function () {
'use strict';
const equalFn = (a, b) => a === b;
const $PROXY = Symbol("solid-proxy");
const $TRACK = Symbol("solid-track");
const $DEVCOMP = Symbol("solid-dev-component");
const signalOptions = {
equals: equalFn
};
let runEffects = runQueue;
const STALE = 1;
const PENDING = 2;
const UNOWNED = {
owned: null,
cleanups: null,
context: null,
owner: null
};
var Owner = null;
let Listener = null;
let Updates = null;
let Effects = null;
let ExecCount = 0;
function createRoot(fn, detachedOwner) {
const listener = Listener,
owner = Owner,
unowned = fn.length === 0,
root = unowned ? UNOWNED : {
owned: null,
cleanups: null,
context: null,
owner: detachedOwner === undefined ? owner : detachedOwner
},
updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));
Owner = root;
Listener = null;
try {
return runUpdates(updateFn, true);
} finally {
Listener = listener;
Owner = owner;
}
}
function createSignal(value, options) {
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
const s = {
value,
observers: null,
observerSlots: null,
comparator: options.equals || undefined
};
const setter = value => {
if (typeof value === "function") {
value = value(s.value);
}
return writeSignal(s, value);
};
return [readSignal.bind(s), setter];
}
function createRenderEffect(fn, value, options) {
const c = createComputation(fn, value, false, STALE);
updateComputation(c);
}
function createEffect(fn, value, options) {
runEffects = runUserEffects;
const c = createComputation(fn, value, false, STALE);
if (!options || !options.render) c.user = true;
Effects ? Effects.push(c) : updateComputation(c);
}
function createMemo(fn, value, options) {
options = options ? Object.assign({}, signalOptions, options) : signalOptions;
const c = createComputation(fn, value, true, 0);
c.observers = null;
c.observerSlots = null;
c.comparator = options.equals || undefined;
updateComputation(c);
return readSignal.bind(c);
}
function batch(fn) {
return runUpdates(fn, false);
}
function untrack(fn) {
if (Listener === null) return fn();
const listener = Listener;
Listener = null;
try {
return fn();
} finally {
Listener = listener;
}
}
function on(deps, fn, options) {
const isArray = Array.isArray(deps);
let prevInput;
let defer = options && options.defer;
return prevValue => {
let input;
if (isArray) {
input = Array(deps.length);
for (let i = 0; i < deps.length; i++) input[i] = deps[i]();
} else input = deps();
if (defer) {
defer = false;
return undefined;
}
const result = untrack(() => fn(input, prevInput, prevValue));
prevInput = input;
return result;
};
}
function onMount(fn) {
createEffect(() => untrack(fn));
}
function onCleanup(fn) {
if (Owner === null) ;else if (Owner.cleanups === null) Owner.cleanups = [fn];else Owner.cleanups.push(fn);
return fn;
}
function getListener() {
return Listener;
}
function getOwner() {
return Owner;
}
function children(fn) {
const children = createMemo(fn);
const memo = createMemo(() => resolveChildren(children()));
memo.toArray = () => {
const c = memo();
return Array.isArray(c) ? c : c != null ? [c] : [];
};
return memo;
}
function readSignal() {
if (this.sources && (this.state)) {
if ((this.state) === STALE) updateComputation(this);else {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(this), false);
Updates = updates;
}
}
if (Listener) {
const sSlot = this.observers ? this.observers.length : 0;
if (!Listener.sources) {
Listener.sources = [this];
Listener.sourceSlots = [sSlot];
} else {
Listener.sources.push(this);
Listener.sourceSlots.push(sSlot);
}
if (!this.observers) {
this.observers = [Listener];
this.observerSlots = [Listener.sources.length - 1];
} else {
this.observers.push(Listener);
this.observerSlots.push(Listener.sources.length - 1);
}
}
return this.value;
}
function writeSignal(node, value, isComp) {
let current = node.value;
if (!node.comparator || !node.comparator(current, value)) {
node.value = value;
if (node.observers && node.observers.length) {
runUpdates(() => {
for (let i = 0; i < node.observers.length; i += 1) {
const o = node.observers[i];
if (!o.state) {
if (o.pure) Updates.push(o);else Effects.push(o);
if (o.observers) markDownstream(o);
}
o.state = STALE;
}
if (Updates.length > 10e5) {
Updates = [];
throw new Error();
}
}, false);
}
}
return value;
}
function updateComputation(node) {
if (!node.fn) return;
cleanNode(node);
const owner = Owner,
listener = Listener,
time = ExecCount;
Listener = Owner = node;
runComputation(node, node.value, time);
Listener = listener;
Owner = owner;
}
function runComputation(node, value, time) {
let nextValue;
try {
nextValue = node.fn(value);
} catch (err) {
if (node.pure) {
{
node.state = STALE;
node.owned && node.owned.forEach(cleanNode);
node.owned = null;
}
}
node.updatedAt = time + 1;
return handleError(err);
}
if (!node.updatedAt || node.updatedAt <= time) {
if (node.updatedAt != null && "observers" in node) {
writeSignal(node, nextValue);
} else node.value = nextValue;
node.updatedAt = time;
}
}
function createComputation(fn, init, pure, state = STALE, options) {
const c = {
fn,
state: state,
updatedAt: null,
owned: null,
sources: null,
sourceSlots: null,
cleanups: null,
value: init,
owner: Owner,
context: null,
pure
};
if (Owner === null) ;else if (Owner !== UNOWNED) {
{
if (!Owner.owned) Owner.owned = [c];else Owner.owned.push(c);
}
}
return c;
}
function runTop(node) {
if ((node.state) === 0) return;
if ((node.state) === PENDING) return lookUpstream(node);
if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node);
const ancestors = [node];
while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {
if (node.state) ancestors.push(node);
}
for (let i = ancestors.length - 1; i >= 0; i--) {
node = ancestors[i];
if ((node.state) === STALE) {
updateComputation(node);
} else if ((node.state) === PENDING) {
const updates = Updates;
Updates = null;
runUpdates(() => lookUpstream(node, ancestors[0]), false);
Updates = updates;
}
}
}
function runUpdates(fn, init) {
if (Updates) return fn();
let wait = false;
if (!init) Updates = [];
if (Effects) wait = true;else Effects = [];
ExecCount++;
try {
const res = fn();
completeUpdates(wait);
return res;
} catch (err) {
if (!wait) Effects = null;
Updates = null;
handleError(err);
}
}
function completeUpdates(wait) {
if (Updates) {
runQueue(Updates);
Updates = null;
}
if (wait) return;
const e = Effects;
Effects = null;
if (e.length) runUpdates(() => runEffects(e), false);
}
function runQueue(queue) {
for (let i = 0; i < queue.length; i++) runTop(queue[i]);
}
function runUserEffects(queue) {
let i,
userLength = 0;
for (i = 0; i < queue.length; i++) {
const e = queue[i];
if (!e.user) runTop(e);else queue[userLength++] = e;
}
for (i = 0; i < userLength; i++) runTop(queue[i]);
}
function lookUpstream(node, ignore) {
node.state = 0;
for (let i = 0; i < node.sources.length; i += 1) {
const source = node.sources[i];
if (source.sources) {
const state = source.state;
if (state === STALE) {
if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source);
} else if (state === PENDING) lookUpstream(source, ignore);
}
}
}
function markDownstream(node) {
for (let i = 0; i < node.observers.length; i += 1) {
const o = node.observers[i];
if (!o.state) {
o.state = PENDING;
if (o.pure) Updates.push(o);else Effects.push(o);
o.observers && markDownstream(o);
}
}
}
function cleanNode(node) {
let i;
if (node.sources) {
while (node.sources.length) {
const source = node.sources.pop(),
index = node.sourceSlots.pop(),
obs = source.observers;
if (obs && obs.length) {
const n = obs.pop(),
s = source.observerSlots.pop();
if (index < obs.length) {
n.sourceSlots[s] = index;
obs[index] = n;
source.observerSlots[index] = s;
}
}
}
}
if (node.owned) {
for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]);
node.owned = null;
}
if (node.cleanups) {
for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i]();
node.cleanups = null;
}
node.state = 0;
node.context = null;
}
function handleError(err) {
throw err;
}
function resolveChildren(children) {
if (typeof children === "function" && !children.length) return resolveChildren(children());
if (Array.isArray(children)) {
const results = [];
for (let i = 0; i < children.length; i++) {
const result = resolveChildren(children[i]);
Array.isArray(result) ? results.push.apply(results, result) : results.push(result);
}
return results;
}
return children;
}
const FALLBACK = Symbol("fallback");
function dispose(d) {
for (let i = 0; i < d.length; i++) d[i]();
}
function mapArray(list, mapFn, options = {}) {
let items = [],
mapped = [],
disposers = [],
len = 0,
indexes = mapFn.length > 1 ? [] : null;
onCleanup(() => dispose(disposers));
return () => {
let newItems = list() || [],
i,
j;
return untrack(() => {
let newLen = newItems.length,
newIndices,
newIndicesNext,
temp,
tempdisposers,
tempIndexes,
start,
end,
newEnd,
item;
if (newLen === 0) {
if (len !== 0) {
dispose(disposers);
disposers = [];
items = [];
mapped = [];
len = 0;
indexes && (indexes = []);
}
if (options.fallback) {
items = [FALLBACK];
mapped[0] = createRoot(disposer => {
disposers[0] = disposer;
return options.fallback();
});
len = 1;
}
} else if (len === 0) {
mapped = new Array(newLen);
for (j = 0; j < newLen; j++) {
items[j] = newItems[j];
mapped[j] = createRoot(mapper);
}
len = newLen;
} else {
temp = new Array(newLen);
tempdisposers = new Array(newLen);
indexes && (tempIndexes = new Array(newLen));
for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++);
for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) {
temp[newEnd] = mapped[end];
tempdisposers[newEnd] = disposers[end];
indexes && (tempIndexes[newEnd] = indexes[end]);
}
newIndices = new Map();
newIndicesNext = new Array(newEnd + 1);
for (j = newEnd; j >= start; j--) {
item = newItems[j];
i = newIndices.get(item);
newIndicesNext[j] = i === undefined ? -1 : i;
newIndices.set(item, j);
}
for (i = start; i <= end; i++) {
item = items[i];
j = newIndices.get(item);
if (j !== undefined && j !== -1) {
temp[j] = mapped[i];
tempdisposers[j] = disposers[i];
indexes && (tempIndexes[j] = indexes[i]);
j = newIndicesNext[j];
newIndices.set(item, j);
} else disposers[i]();
}
for (j = start; j < newLen; j++) {
if (j in temp) {
mapped[j] = temp[j];
disposers[j] = tempdisposers[j];
if (indexes) {
indexes[j] = tempIndexes[j];
indexes[j](j);
}
} else mapped[j] = createRoot(mapper);
}
mapped = mapped.slice(0, len = newLen);
items = newItems.slice(0);
}
return mapped;
});
function mapper(disposer) {
disposers[j] = disposer;
if (indexes) {
const [s, set] = createSignal(j);
indexes[j] = set;
return mapFn(newItems[j], s);
}
return mapFn(newItems[j]);
}
};
}
function createComponent(Comp, props) {
return untrack(() => Comp(props || {}));
}
function trueFn() {
return true;
}
const propTraps = {
get(_, property, receiver) {
if (property === $PROXY) return receiver;
return _.get(property);
},
has(_, property) {
if (property === $PROXY) return true;
return _.has(property);
},
set: trueFn,
deleteProperty: trueFn,
getOwnPropertyDescriptor(_, property) {
return {
configurable: true,
enumerable: true,
get() {
return _.get(property);
},
set: trueFn,
deleteProperty: trueFn
};
},
ownKeys(_) {
return _.keys();
}
};
function splitProps(props, ...keys) {
const blocked = new Set(keys.flat());
if ($PROXY in props) {
const res = keys.map(k => {
return new Proxy({
get(property) {
return k.includes(property) ? props[property] : undefined;
},
has(property) {
return k.includes(property) && property in props;
},
keys() {
return k.filter(property => property in props);
}
}, propTraps);
});
res.push(new Proxy({
get(property) {
return blocked.has(property) ? undefined : props[property];
},
has(property) {
return blocked.has(property) ? false : property in props;
},
keys() {
return Object.keys(props).filter(k => !blocked.has(k));
}
}, propTraps));
return res;
}
const descriptors = Object.getOwnPropertyDescriptors(props);
keys.push(Object.keys(descriptors).filter(k => !blocked.has(k)));
return keys.map(k => {
const clone = {};
for (let i = 0; i < k.length; i++) {
const key = k[i];
if (!(key in props)) continue;
Object.defineProperty(clone, key, descriptors[key] ? descriptors[key] : {
get() {
return props[key];
},
set() {
return true;
},
enumerable: true
});
}
return clone;
});
}
const narrowedError = name => `Stale read from <${name}>.`;
function For(props) {
const fallback = "fallback" in props && {
fallback: () => props.fallback
};
return createMemo(mapArray(() => props.each, props.children, fallback || undefined));
}
function Show(props) {
const keyed = props.keyed;
const condition = createMemo(() => props.when, undefined, {
equals: (a, b) => keyed ? a === b : !a === !b
});
return createMemo(() => {
const c = condition();
if (c) {
const child = props.children;
const fn = typeof child === "function" && child.length > 0;
return fn ? untrack(() => child(keyed ? c : () => {
if (!untrack(condition)) throw narrowedError("Show");
return props.when;
})) : child;
}
return props.fallback;
}, undefined, undefined);
}
function Switch(props) {
let keyed = false;
const equals = (a, b) => a[0] === b[0] && (keyed ? a[1] === b[1] : !a[1] === !b[1]) && a[2] === b[2];
const conditions = children(() => props.children),
evalConditions = createMemo(() => {
let conds = conditions();
if (!Array.isArray(conds)) conds = [conds];
for (let i = 0; i < conds.length; i++) {
const c = conds[i].when;
if (c) {
keyed = !!conds[i].keyed;
return [i, c, conds[i]];
}
}
return [-1];
}, undefined, {
equals
});
return createMemo(() => {
const [index, when, cond] = evalConditions();
if (index < 0) return props.fallback;
const c = cond.children;
const fn = typeof c === "function" && c.length > 0;
return fn ? untrack(() => c(keyed ? when : () => {
if (untrack(evalConditions)[0] !== index) throw narrowedError("Match");
return cond.when;
})) : c;
}, undefined, undefined);
}
function Match(props) {
return props;
}
const booleans = ["allowfullscreen", "async", "autofocus", "autoplay", "checked", "controls", "default", "disabled", "formnovalidate", "hidden", "indeterminate", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "seamless", "selected"];
const Properties = /*#__PURE__*/new Set(["className", "value", "readOnly", "formNoValidate", "isMap", "noModule", "playsInline", ...booleans]);
const ChildProperties = /*#__PURE__*/new Set(["innerHTML", "textContent", "innerText", "children"]);
const Aliases = /*#__PURE__*/Object.assign(Object.create(null), {
className: "class",
htmlFor: "for"
});
const PropAliases = /*#__PURE__*/Object.assign(Object.create(null), {
class: "className",
formnovalidate: {
$: "formNoValidate",
BUTTON: 1,
INPUT: 1
},
ismap: {
$: "isMap",
IMG: 1
},
nomodule: {
$: "noModule",
SCRIPT: 1
},
playsinline: {
$: "playsInline",
VIDEO: 1
},
readonly: {
$: "readOnly",
INPUT: 1,
TEXTAREA: 1
}
});
function getPropAlias(prop, tagName) {
const a = PropAliases[prop];
return typeof a === "object" ? a[tagName] ? a["$"] : undefined : a;
}
const DelegatedEvents = /*#__PURE__*/new Set(["beforeinput", "click", "dblclick", "contextmenu", "focusin", "focusout", "input", "keydown", "keyup", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "pointerdown", "pointermove", "pointerout", "pointerover", "pointerup", "touchend", "touchmove", "touchstart"]);
const SVGElements = /*#__PURE__*/new Set(["altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignObject", "g", "glyph", "glyphRef", "hkern", "image", "line", "linearGradient", "marker", "mask", "metadata", "missing-glyph", "mpath", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", "set", "stop", "svg", "switch", "symbol", "text", "textPath", "tref", "tspan", "use", "view", "vkern"]);
const SVGNamespace = {
xlink: "http://www.w3.org/1999/xlink",
xml: "http://www.w3.org/XML/1998/namespace"
};
function reconcileArrays(parentNode, a, b) {
let bLength = b.length,
aEnd = a.length,
bEnd = bLength,
aStart = 0,
bStart = 0,
after = a[aEnd - 1].nextSibling,
map = null;
while (aStart < aEnd || bStart < bEnd) {
if (a[aStart] === b[bStart]) {
aStart++;
bStart++;
continue;
}
while (a[aEnd - 1] === b[bEnd - 1]) {
aEnd--;
bEnd--;
}
if (aEnd === aStart) {
const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after;
while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node);
} else if (bEnd === bStart) {
while (aStart < aEnd) {
if (!map || !map.has(a[aStart])) a[aStart].remove();
aStart++;
}
} else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
const node = a[--aEnd].nextSibling;
parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling);
parentNode.insertBefore(b[--bEnd], node);
a[aEnd] = b[bEnd];
} else {
if (!map) {
map = new Map();
let i = bStart;
while (i < bEnd) map.set(b[i], i++);
}
const index = map.get(a[aStart]);
if (index != null) {
if (bStart < index && index < bEnd) {
let i = aStart,
sequence = 1,
t;
while (++i < aEnd && i < bEnd) {
if ((t = map.get(a[i])) == null || t !== index + sequence) break;
sequence++;
}
if (sequence > index - bStart) {
const node = a[aStart];
while (bStart < index) parentNode.insertBefore(b[bStart++], node);
} else parentNode.replaceChild(b[bStart++], a[aStart++]);
} else aStart++;
} else a[aStart++].remove();
}
}
}
const $$EVENTS = "_$DX_DELEGATE";
function render(code, element, init, options = {}) {
let disposer;
createRoot(dispose => {
disposer = dispose;
element === document ? code() : insert(element, code(), element.firstChild ? null : undefined, init);
}, options.owner);
return () => {
disposer();
element.textContent = "";
};
}
function template(html, isCE, isSVG) {
let node;
const create = () => {
const t = document.createElement("template");
t.innerHTML = html;
return isSVG ? t.content.firstChild.firstChild : t.content.firstChild;
};
const fn = isCE ? () => (node || (node = create())).cloneNode(true) : () => untrack(() => document.importNode(node || (node = create()), true));
fn.cloneNode = fn;
return fn;
}
function delegateEvents(eventNames, document = window.document) {
const e = document[$$EVENTS] || (document[$$EVENTS] = new Set());
for (let i = 0, l = eventNames.length; i < l; i++) {
const name = eventNames[i];
if (!e.has(name)) {
e.add(name);
document.addEventListener(name, eventHandler);
}
}
}
function setAttribute(node, name, value) {
if (value == null) node.removeAttribute(name);else node.setAttribute(name, value);
}
function setAttributeNS(node, namespace, name, value) {
if (value == null) node.removeAttributeNS(namespace, name);else node.setAttributeNS(namespace, name, value);
}
function className(node, value) {
if (value == null) node.removeAttribute("class");else node.className = value;
}
function addEventListener(node, name, handler, delegate) {
if (delegate) {
if (Array.isArray(handler)) {
node[`$$${name}`] = handler[0];
node[`$$${name}Data`] = handler[1];
} else node[`$$${name}`] = handler;
} else if (Array.isArray(handler)) {
const handlerFn = handler[0];
node.addEventListener(name, handler[0] = e => handlerFn.call(node, handler[1], e));
} else node.addEventListener(name, handler);
}
function classList(node, value, prev = {}) {
const classKeys = Object.keys(value || {}),
prevKeys = Object.keys(prev);
let i, len;
for (i = 0, len = prevKeys.length; i < len; i++) {
const key = prevKeys[i];
if (!key || key === "undefined" || value[key]) continue;
toggleClassKey(node, key, false);
delete prev[key];
}
for (i = 0, len = classKeys.length; i < len; i++) {
const key = classKeys[i],
classValue = !!value[key];
if (!key || key === "undefined" || prev[key] === classValue || !classValue) continue;
toggleClassKey(node, key, true);
prev[key] = classValue;
}
return prev;
}
function style(node, value, prev) {
if (!value) return prev ? setAttribute(node, "style") : value;
const nodeStyle = node.style;
if (typeof value === "string") return nodeStyle.cssText = value;
typeof prev === "string" && (nodeStyle.cssText = prev = undefined);
prev || (prev = {});
value || (value = {});
let v, s;
for (s in prev) {
value[s] == null && nodeStyle.removeProperty(s);
delete prev[s];
}
for (s in value) {
v = value[s];
if (v !== prev[s]) {
nodeStyle.setProperty(s, v);
prev[s] = v;
}
}
return prev;
}
function spread(node, props = {}, isSVG, skipChildren) {
const prevProps = {};
if (!skipChildren) {
createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children));
}
createRenderEffect(() => props.ref && props.ref(node));
createRenderEffect(() => assign(node, props, isSVG, true, prevProps, true));
return prevProps;
}
function insert(parent, accessor, marker, initial) {
if (marker !== undefined && !initial) initial = [];
if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
createRenderEffect(current => insertExpression(parent, accessor(), current, marker), initial);
}
function assign(node, props, isSVG, skipChildren, prevProps = {}, skipRef = false) {
props || (props = {});
for (const prop in prevProps) {
if (!(prop in props)) {
if (prop === "children") continue;
prevProps[prop] = assignProp(node, prop, null, prevProps[prop], isSVG, skipRef);
}
}
for (const prop in props) {
if (prop === "children") {
if (!skipChildren) insertExpression(node, props.children);
continue;
}
const value = props[prop];
prevProps[prop] = assignProp(node, prop, value, prevProps[prop], isSVG, skipRef);
}
}
function toPropertyName(name) {
return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase());
}
function toggleClassKey(node, key, value) {
const classNames = key.trim().split(/\s+/);
for (let i = 0, nameLen = classNames.length; i < nameLen; i++) node.classList.toggle(classNames[i], value);
}
function assignProp(node, prop, value, prev, isSVG, skipRef) {
let isCE, isProp, isChildProp, propAlias, forceProp;
if (prop === "style") return style(node, value, prev);
if (prop === "classList") return classList(node, value, prev);
if (value === prev) return prev;
if (prop === "ref") {
if (!skipRef) value(node);
} else if (prop.slice(0, 3) === "on:") {
const e = prop.slice(3);
prev && node.removeEventListener(e, prev);
value && node.addEventListener(e, value);
} else if (prop.slice(0, 10) === "oncapture:") {
const e = prop.slice(10);
prev && node.removeEventListener(e, prev, true);
value && node.addEventListener(e, value, true);
} else if (prop.slice(0, 2) === "on") {
const name = prop.slice(2).toLowerCase();
const delegate = DelegatedEvents.has(name);
if (!delegate && prev) {
const h = Array.isArray(prev) ? prev[0] : prev;
node.removeEventListener(name, h);
}
if (delegate || value) {
addEventListener(node, name, value, delegate);
delegate && delegateEvents([name]);
}
} else if (prop.slice(0, 5) === "attr:") {
setAttribute(node, prop.slice(5), value);
} else if ((forceProp = prop.slice(0, 5) === "prop:") || (isChildProp = ChildProperties.has(prop)) || !isSVG && ((propAlias = getPropAlias(prop, node.tagName)) || (isProp = Properties.has(prop))) || (isCE = node.nodeName.includes("-"))) {
if (forceProp) {
prop = prop.slice(5);
isProp = true;
}
if (prop === "class" || prop === "className") className(node, value);else if (isCE && !isProp && !isChildProp) node[toPropertyName(prop)] = value;else node[propAlias || prop] = value;
} else {
const ns = isSVG && prop.indexOf(":") > -1 && SVGNamespace[prop.split(":")[0]];
if (ns) setAttributeNS(node, ns, prop, value);else setAttribute(node, Aliases[prop] || prop, value);
}
return value;
}
function eventHandler(e) {
const key = `$$${e.type}`;
let node = e.composedPath && e.composedPath()[0] || e.target;
if (e.target !== node) {
Object.defineProperty(e, "target", {
configurable: true,
value: node
});
}
Object.defineProperty(e, "currentTarget", {
configurable: true,
get() {
return node || document;
}
});
while (node) {
const handler = node[key];
if (handler && !node.disabled) {
const data = node[`${key}Data`];
data !== undefined ? handler.call(node, data, e) : handler.call(node, e);
if (e.cancelBubble) return;
}
node = node._$host || node.parentNode || node.host;
}
}
function insertExpression(parent, value, current, marker, unwrapArray) {
while (typeof current === "function") current = current();
if (value === current) return current;
const t = typeof value,
multi = marker !== undefined;
parent = multi && current[0] && current[0].parentNode || parent;
if (t === "string" || t === "number") {
if (t === "number") value = value.toString();
if (multi) {
let node = current[0];
if (node && node.nodeType === 3) {
node.data = value;
} else node = document.createTextNode(value);
current = cleanChildren(parent, current, marker, node);
} else {
if (current !== "" && typeof current === "string") {
current = parent.firstChild.data = value;
} else current = parent.textContent = value;
}
} else if (value == null || t === "boolean") {
current = cleanChildren(parent, current, marker);
} else if (t === "function") {
createRenderEffect(() => {
let v = value();
while (typeof v === "function") v = v();
current = insertExpression(parent, v, current, marker);
});
return () => current;
} else if (Array.isArray(value)) {
const array = [];
const currentArray = current && Array.isArray(current);
if (normalizeIncomingArray(array, value, current, unwrapArray)) {
createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));
return () => current;
}
if (array.length === 0) {
current = cleanChildren(parent, current, marker);
if (multi) return current;
} else if (currentArray) {
if (current.length === 0) {
appendNodes(parent, array, marker);
} else reconcileArrays(parent, current, array);
} else {
current && cleanChildren(parent);
appendNodes(parent, array);
}
current = array;
} else if (value instanceof Node) {
if (Array.isArray(current)) {
if (multi) return current = cleanChildren(parent, current, marker, value);
cleanChildren(parent, current, null, value);
} else if (current == null || current === "" || !parent.firstChild) {
parent.appendChild(value);
} else parent.replaceChild(value, parent.firstChild);
current = value;
} else console.warn(`Unrecognized value. Skipped inserting`, value);
return current;
}
function normalizeIncomingArray(normalized, array, current, unwrap) {
let dynamic = false;
for (let i = 0, len = array.length; i < len; i++) {
let item = array[i],
prev = current && current[i];
if (item instanceof Node) {
normalized.push(item);
} else if (item == null || item === true || item === false) ;else if (Array.isArray(item)) {
dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;
} else if (typeof item === "function") {
if (unwrap) {
while (typeof item === "function") item = item();
dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic;
} else {
normalized.push(item);
dynamic = true;
}
} else {
const value = String(item);
if (prev && prev.nodeType === 3) {
prev.data = value;
normalized.push(prev);
} else normalized.push(document.createTextNode(value));
}
}
return dynamic;
}
function appendNodes(parent, array, marker = null) {
for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker);
}
function cleanChildren(parent, current, marker, replacement) {
if (marker === undefined) return parent.textContent = "";
const node = replacement || document.createTextNode("");
if (current.length) {
let inserted = false;
for (let i = current.length - 1; i >= 0; i--) {
const el = current[i];
if (node !== el) {
const isParent = el.parentNode === parent;
if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);else isParent && el.remove();
} else inserted = true;
}
} else parent.insertBefore(node, marker);
return [node];
}
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
function createElement(tagName, isSVG = false) {
return isSVG ? document.createElementNS(SVG_NAMESPACE, tagName) : document.createElement(tagName);
}
function Dynamic(props) {
const [p, others] = splitProps(props, ["component"]);
const cached = createMemo(() => p.component);
return createMemo(() => {
const component = cached();
switch (typeof component) {
case "function":
Object.assign(component, {
[$DEVCOMP]: true
});
return untrack(() => component(others));
case "string":
const isSvg = SVGElements.has(component);
const el = createElement(component, isSvg);
spread(el, others, isSvg);
return el;
}
});
}
var throttle = (callback, wait) => {
let isThrottled = false,
timeoutId,
lastArgs;
const throttled = (...args) => {
lastArgs = args;
if (isThrottled) return;
isThrottled = true;
timeoutId = setTimeout(() => {
callback(...lastArgs);
isThrottled = false;
}, wait);
};
const clear = () => {
clearTimeout(timeoutId);
isThrottled = false;
};
if (getOwner()) onCleanup(clear);
return Object.assign(throttled, {
clear
});
};
var access = v => typeof v === "function" && !v.length ? v() : v;
var asArray = value => Array.isArray(value) ? value : value ? [value] : [];
function createGMSignal(key, initialValue) {
const [signal, setSignal] = createSignal(initialValue);
let listener;
GMP.addValueChangeListener?.(key, (name, oldValue, newValue, remote) => {
if (name === key && (remote === void 0 || remote === true)) read(newValue);
}).then(l => listener = l);
let effectPaused = false;
createEffect(on(signal, () => {
if (effectPaused) return;
if (signal() == null) {
GMP.deleteValue(key);
effectPaused = true;
setSignal(() => initialValue);
effectPaused = false;
} else {
GMP.setValue(key, signal());
}
}, {
defer: true
}));
async function read(newValue) {
effectPaused = true;
const rawValue = newValue ?? (await GMP.getValue(key));
if (rawValue == null) setSignal(() => initialValue);else setSignal(() => rawValue);
effectPaused = false;
}
const [isReady, setIsReady] = createSignal(false);
signal.isReady = isReady;
signal.ready = read().then(() => {
setIsReady(true);
});
onCleanup(() => {
if (listener) GMP.removeValueChangeListener?.(listener);
});
return [signal, setSignal];
}
const [detectionResolution, setDetectionResolution] = createGMSignal("detectionResolution", "M");
const [textDetector, setTextDetector] = createGMSignal("textDetector", "default");
const [translatorService, setTranslatorService] = createGMSignal("translator", "youdao");
const [renderTextOrientation, setRenderTextOrientation] = createGMSignal("renderTextOrientation", "auto");
const [targetLang, setTargetLang] = createGMSignal("targetLang", "");
const [scriptLang, setScriptLang] = createGMSignal("scriptLanguage", "");
const [keepInstances, setKeepInstances] = createGMSignal("keepInstances", "until-reload");
const storageReady = Promise.all([detectionResolution.ready, textDetector.ready, translatorService.ready, renderTextOrientation.ready, targetLang.ready, scriptLang.ready]);
var data$1 = { common:{ source:{ "download-image":"正在拉取原图",
"download-image-progress":"正在拉取原图({progress})",
"download-image-error":"拉取原图出错" },
client:{ submit:"正在提交翻译",
"submit-progress":"正在提交翻译({progress})",
"submit-error":"提交翻译出错",
"download-image":"正在下载图片",
"download-image-progress":"正在下载图片({progress})",
"download-image-error":"下载图片出错",
resize:"正在缩放图片",
merging:"正在合并图层" },
status:{ "default":"未知状态",
pending:"正在等待",
"pending-pos":"正在等待,列队还有 {pos} 张图片",
upscaling:"正在放大图片",
detection:"正在检测文本",
ocr:"正在识别文本",
"mask-generation":"正在生成文本掩码",
inpainting:"正在修补图片",
translating:"正在翻译文本",
rendering:"正在渲染",
downscaling:"正在缩小图片",
finished:"正在整理结果",
error:"翻译出错",
"error-lang":"你选择的翻译服务不支持你选择的语言",
"error-translating":"翻译服务没有返回任何文本",
"error-with-id":"翻译出错 (ID: {id})" },
control:{ translate:"翻译",
batch:"翻译全部 ({count})",
reset:"还原" },
batch:{ progress:"翻译中 ({count}/{total})",
finish:"翻译完成",
error:"翻译完成(有失败)" } },
settings:{ title:"Cotrans 图片翻译器设置",
"inline-options-title":"设置当前翻译",
"detection-resolution":"文本扫描清晰度",
"text-detector":"文本扫描器",
"text-detector-options":{ "default":"默认" },
translator:"翻译服务",
"render-text-orientation":"渲染字体方向",
"render-text-orientation-options":{ auto:"跟随原文本",
horizontal:"仅限水平",
vertical:"仅限垂直" },
"target-language":"翻译语言",
"target-language-options":{ auto:"跟随网页语言" },
"script-language":"用户脚本语言",
"script-language-options":{ auto:"跟随网页语言" },
reset:"重置所有设置",
"detection-resolution-desc":"设置检测图片文本所用的清晰度,小文字适合使用更高的清晰度。",
"text-detector-desc":"设置使用的文本扫描器。",
"translator-desc":"设置翻译图片所用的翻译服务。",
"render-text-orientation-desc":"设置嵌字的文本方向。",
"target-language-desc":"设置图片翻译后的语言。",
"script-language-desc":"设置此用户脚本的语言。",
"translator-options":{ none:"None (删除文字)" },
"keep-instances-options":{ "until-reload":"直到页面刷新",
"until-navigate":"直到下次跳转" },
"keep-instances":"保留翻译进度",
"keep-instances-desc":"设置翻译进度的保留时间。 翻译进度即图片的翻译状态和翻译结果。 保留更多的翻译进度会占用更多的内存。" },
sponsor:{ text:"制作不易,请考虑赞助我们!" } };
const zhCN = data$1;
var data = { common:{ source:{ "download-image":"Downloading original image",
"download-image-progress":"Downloading original image ({progress})",
"download-image-error":"Error during original image download" },
client:{ submit:"Submitting translation",
"submit-progress":"Submitting translation ({progress})",
"submit-error":"Error during translation submission",
"download-image":"Downloading translated image",
"download-image-progress":"Downloading translated image ({progress})",
"download-image-error":"Error during translated image download",
resize:"Resizing image",
merging:"Merging layers" },
status:{ "default":"Unknown status",
pending:"Pending",
"pending-pos":"Pending, {pos} in queue",
upscaling:"Upscaling",
detection:"Detecting text",
ocr:"Scanning text",
"mask-generation":"Generating mask",
inpainting:"Inpainting",
translating:"Translating",
rendering:"Rendering",
downscaling:"Downscaling",
finished:"Finishing",
error:"Error during translation",
"error-lang":"Your target language is not supported by the chosen translator",
"error-translating":"Did not get any text back from the text translation service",
"error-with-id":"Error during translation (ID: {id})" },
control:{ translate:"Translate",
batch:"Translate all ({count})",
reset:"Reset" },
batch:{ progress:"Translating ({count}/{total} finished)",
finish:"Translation finished",
error:"Translation finished with errors" } },
settings:{ "detection-resolution":"Text detection resolution",
"render-text-orientation":"Render text orientation",
"render-text-orientation-options":{ auto:"Follow source",
horizontal:"Horizontal only",
vertical:"Vertical only" },
reset:"Reset Settings",
"target-language":"Translate target language",
"target-language-options":{ auto:"Follow website" },
"text-detector":"Text detector",
"text-detector-options":{ "default":"Default" },
title:"Cotrans Manga Translator Settings",
translator:"Translator",
"script-language":"Userscript language",
"script-language-options":{ auto:"Follow website language" },
"inline-options-title":"Current Settings",
"detection-resolution-desc":"The resolution used to scan texts on an image, higher value are better suited for smaller texts.",
"script-language-desc":"Language of this userscript.",
"render-text-orientation-desc":"Overwrite the orientation of texts rendered in the translated image.",
"target-language-desc":"The language that images are translated to.",
"text-detector-desc":"The detector used to scan texts in an image.",
"translator-desc":"The translate service used to translate texts.",
"translator-options":{ none:"None (remove texts)" },
"keep-instances-options":{ "until-reload":"Until page reload",
"until-navigate":"Until next navigation" },
"keep-instances":"Keep translation instances",
"keep-instances-desc":"How long before a translation instance is disposed. A translation instance includes the translation state of an image, that is, whether the image is translated or not, and the translation result. Keeping more translation instances will result in more memory consumption." },
sponsor:{ text:"If you find this script helpful, please consider supporting us!" } };
const enUS = data;
const messages = {
"zh-CN": zhCN,
"en-US": enUS
};
function tryMatchLang(lang2) {
if (lang2.startsWith("zh")) return "zh-CN";
if (lang2.startsWith("en")) return "en-US";
return "en-US";
}
const [realLang, setRealLang] = createSignal(navigator.language);
const lang = createMemo(() => scriptLang() || tryMatchLang(realLang()));
function t(key_, props = {}) {
return createMemo(() => {
const key = access(key_);
const segments = key.split(".");
const msg = segments.reduce((obj, k) => obj[k], messages[lang()]) ?? segments.reduce((obj, k) => obj[k], messages["zh-CN"]);
if (!msg) return key;
return msg.replace(/\{([^}]+)\}/g, (_, k) => String(access(access(props)[k])) ?? "");
});
}
let langEL;
let langObserver;
function changeLangEl(el) {
if (langEL === el) return;
if (langObserver) langObserver.disconnect();
langObserver = new MutationObserver(mutations => {
for (const mutation of mutations) {
if (mutation.type === "attributes" && mutation.attributeName === "lang") {
const target = mutation.target;
if (target.lang) setRealLang(target.lang);
break;
}
}
});
langObserver.observe(el, {
attributes: true
});
langEL = el;
setRealLang(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";
case "uk":
return "UKR";
}
return "ENG";
} catch (e) {
return "ENG";
}
}
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);
}
DelegatedEvents.clear();
function createScopedInstance(cb) {
return createRoot(dispose => {
const instance = cb();
return {
...instance,
dispose
};
});
}
let currentURL;
let translator$2;
let settingsInjector$2;
async function start(translators, settingsInjectors) {
await storageReady;
async function onUpdate() {
await new Promise(resolve => (queueMicrotask ?? setTimeout)(resolve));
if (currentURL !== location.href) {
currentURL = location.href;
checkCSS();
changeLangEl(document.documentElement);
if (translator$2?.canKeep?.(currentURL)) {
translator$2.onURLChange?.(currentURL);
} else {
translator$2?.dispose();
translator$2 = void 0;
const url = new URL(location.href);
const matched = translators.find(t => t.match(url));
if (matched) translator$2 = createScopedInstance(matched.mount);
}
if (settingsInjector$2?.canKeep?.(currentURL)) {
settingsInjector$2.onURLChange?.(currentURL);
} else {
settingsInjector$2?.dispose();
settingsInjector$2 = void 0;
const url = new URL(location.href);
const matched = settingsInjectors.find(t => t.match(url));
if (matched) settingsInjector$2 = createScopedInstance(matched.mount);
}
}
}
if (window.onurlchange === null) {
window.addEventListener("urlchange", onUpdate);
} else {
const installObserver = new MutationObserver(throttle(onUpdate, 200));
installObserver.observe(document.body, {
childList: true,
subtree: true
});
}
onUpdate();
}
// src/index.ts
var triggerOptions = {
equals: false
};
var triggerCacheOptions = triggerOptions;
var TriggerCache = class {
#map;
constructor(mapConstructor = Map) {
this.#map = new mapConstructor();
}
dirty(key) {
this.#map.get(key)?.$$();
}
track(key) {
if (!getListener()) return;
let trigger = this.#map.get(key);
if (!trigger) {
const [$, $$] = createSignal(void 0, triggerCacheOptions);
this.#map.set(key, trigger = {
$,
$$,
n: 1
});
} else trigger.n++;
onCleanup(() => {
if (trigger.n-- === 1) queueMicrotask(() => trigger.n === 0 && this.#map.delete(key));
});
trigger.$();
}
};
// src/index.ts
var $KEYS = Symbol("track-keys");
var ReactiveMap = class extends Map {
#keyTriggers = new TriggerCache();
#valueTriggers = new TriggerCache();
constructor(initial) {
super();
if (initial) for (const v of initial) super.set(v[0], v[1]);
}
// reads
has(key) {
this.#keyTriggers.track(key);
return super.has(key);
}
get(key) {
this.#valueTriggers.track(key);
return super.get(key);
}
get size() {
this.#keyTriggers.track($KEYS);
return super.size;
}
keys() {
this.#keyTriggers.track($KEYS);
return super.keys();
}
values() {
this.#keyTriggers.track($KEYS);
for (const v of super.keys()) this.#valueTriggers.track(v);
return super.values();
}
entries() {
this.#keyTriggers.track($KEYS);
for (const v of super.keys()) this.#valueTriggers.track(v);
return super.entries();
}
// writes
set(key, value) {
batch(() => {
if (super.has(key)) {
if (super.get(key) === value) return;
} else {
this.#keyTriggers.dirty(key);
this.#keyTriggers.dirty($KEYS);
}
this.#valueTriggers.dirty(key);
super.set(key, value);
});
return this;
}
delete(key) {
const r = super.delete(key);
if (r) {
batch(() => {
this.#keyTriggers.dirty(key);
this.#keyTriggers.dirty($KEYS);
this.#valueTriggers.dirty(key);
});
}
return r;
}
clear() {
if (super.size) {
batch(() => {
for (const v of super.keys()) {
this.#keyTriggers.dirty(v);
this.#valueTriggers.dirty(v);
}
super.clear();
this.#keyTriggers.dirty($KEYS);
});
}
}
// callback
forEach(callbackfn) {
this.#keyTriggers.track($KEYS);
super.forEach((value, key) => callbackfn(value, key, this));
}
[Symbol.iterator]() {
return this.entries();
}
};
// src/index.ts
function createMutationObserver(initial, b, c) {
let defaultOptions, callback;
const isSupported = typeof window !== "undefined" && "MutationObserver" in window;
if (typeof b === "function") {
defaultOptions = {};
callback = b;
} else {
defaultOptions = b;
callback = c;
}
const instance = isSupported ? new MutationObserver(callback) : void 0;
const add = (el, options) => instance?.observe(el, access(options) ?? defaultOptions);
const start = () => {
asArray(access(initial)).forEach(item => {
item instanceof Node ? add(item, defaultOptions) : add(item[0], item[1]);
});
};
const stop = () => instance?.disconnect();
onMount(start);
onCleanup(stop);
return [add, {
start,
stop,
instance,
isSupported
}];
}
const _tmpl$$8 = /*#__PURE__*/template(`
edition, v