|
|
@ -1,62 +1,62 @@ |
|
|
|
import * as tf from '@tensorflow/tfjs' |
|
|
|
import { setWasmPaths } from '@tensorflow/tfjs-backend-wasm' |
|
|
|
import modelJSON from './model.json' |
|
|
|
import ccl from './ccl' |
|
|
|
import * as tf from '@tensorflow/tfjs'; |
|
|
|
import { setWasmPaths } from '@tensorflow/tfjs-backend-wasm'; |
|
|
|
import modelJSON from './model.json'; |
|
|
|
import ccl from './ccl'; |
|
|
|
|
|
|
|
const charset = [' ', '0', '2', '4', '5', '8', 'A', 'D', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y'] |
|
|
|
let weightsData |
|
|
|
let model |
|
|
|
const charset = [' ', '0', '2', '4', '5', '8', 'A', 'D', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y']; |
|
|
|
let weightsData; |
|
|
|
let model; |
|
|
|
|
|
|
|
tf.enableProdMode() |
|
|
|
tf.enableProdMode(); |
|
|
|
|
|
|
|
const wasmToUrl = wasm => { |
|
|
|
const blb = new Blob([wasm], { type: 'application/wasm' }) |
|
|
|
return URL.createObjectURL(blb) |
|
|
|
} |
|
|
|
const blb = new Blob([wasm], { type: 'application/wasm' }); |
|
|
|
return URL.createObjectURL(blb); |
|
|
|
}; |
|
|
|
|
|
|
|
const backendloaded = (async () => { |
|
|
|
try { |
|
|
|
// dead code elimination should occur here
|
|
|
|
// eslint-disable-next-line camelcase
|
|
|
|
if (execution_mode === 'userscript' || execution_mode === 'test') { |
|
|
|
weightsData = (await import('./model.weights.bin')).default |
|
|
|
const tfwasmthreadedsimd = (await import('./tfjs-backend-wasm-threaded-simd.wasm')).default |
|
|
|
const tfwasmsimd = (await import('./tfjs-backend-wasm-simd.wasm')).default |
|
|
|
const tfwasm = (await import('./tfjs-backend-wasm.wasm')).default |
|
|
|
weightsData = (await import('./model.weights.bin')).default; |
|
|
|
const tfwasmthreadedsimd = (await import('./tfjs-backend-wasm-threaded-simd.wasm')).default; |
|
|
|
const tfwasmsimd = (await import('./tfjs-backend-wasm-simd.wasm')).default; |
|
|
|
const tfwasm = (await import('./tfjs-backend-wasm.wasm')).default; |
|
|
|
setWasmPaths({ |
|
|
|
'tfjs-backend-wasm.wasm': wasmToUrl(tfwasm), |
|
|
|
'tfjs-backend-wasm-simd.wasm': wasmToUrl(tfwasmsimd), |
|
|
|
'tfjs-backend-wasm-threaded-simd.wasm': wasmToUrl(tfwasmthreadedsimd) |
|
|
|
}) |
|
|
|
}); |
|
|
|
} else { |
|
|
|
weightsData = await (await fetch(chrome.runtime.getURL('./model.weights.bin'))).text() |
|
|
|
weightsData = await (await fetch(chrome.runtime.getURL('./model.weights.bin'))).text(); |
|
|
|
const args = { |
|
|
|
'tfjs-backend-wasm.wasm': chrome.runtime.getURL('tfjs-backend-wasm.wasm'), |
|
|
|
'tfjs-backend-wasm-simd.wasm': chrome.runtime.getURL('tfjs-backend-wasm-simd.wasm'), |
|
|
|
'tfjs-backend-wasm-threaded-simd.wasm': chrome.runtime.getURL('tfjs-backend-wasm-threaded-simd.wasm') |
|
|
|
} |
|
|
|
setWasmPaths(args) |
|
|
|
}; |
|
|
|
setWasmPaths(args); |
|
|
|
} |
|
|
|
const l = await tf.setBackend('wasm') |
|
|
|
console.log('tf backend loaded', l) |
|
|
|
const l = await tf.setBackend('wasm'); |
|
|
|
console.log('tf backend loaded', l); |
|
|
|
} catch (err) { |
|
|
|
console.log('tf err', err) |
|
|
|
console.log('tf err', err); |
|
|
|
} |
|
|
|
})() |
|
|
|
})(); |
|
|
|
|
|
|
|
function toggle (obj, v) { |
|
|
|
if (v) obj.style.display = '' |
|
|
|
else obj.style.display = 'none' |
|
|
|
function toggle(obj, v) { |
|
|
|
if (v) obj.style.display = ''; |
|
|
|
else obj.style.display = 'none'; |
|
|
|
} |
|
|
|
|
|
|
|
function base64ToArray (base64) { |
|
|
|
const binaryString = window.atob(base64) |
|
|
|
const len = binaryString.length |
|
|
|
const bytes = new Uint8Array(len) |
|
|
|
function base64ToArray(base64) { |
|
|
|
const binaryString = window.atob(base64); |
|
|
|
const len = binaryString.length; |
|
|
|
const bytes = new Uint8Array(len); |
|
|
|
for (let i = 0; i < len; i++) { |
|
|
|
bytes[i] = binaryString.charCodeAt(i) |
|
|
|
bytes[i] = binaryString.charCodeAt(i); |
|
|
|
} |
|
|
|
return bytes.buffer |
|
|
|
return bytes.buffer; |
|
|
|
} |
|
|
|
|
|
|
|
const iohander = { |
|
|
@ -69,100 +69,100 @@ const iohander = { |
|
|
|
format: modelJSON.format, |
|
|
|
generatedBy: modelJSON.generatedBy, |
|
|
|
convertedBy: modelJSON.convertedBy |
|
|
|
}) |
|
|
|
}) |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
async function load () { |
|
|
|
const uploadJSONInput = document.getElementById('upload-json') |
|
|
|
const uploadWeightsInput = document.getElementById('upload-weights-1') |
|
|
|
model = await tf.loadLayersModel(iohander) |
|
|
|
return model |
|
|
|
async function load() { |
|
|
|
const uploadJSONInput = document.getElementById('upload-json'); |
|
|
|
const uploadWeightsInput = document.getElementById('upload-weights-1'); |
|
|
|
model = await tf.loadLayersModel(iohander); |
|
|
|
return model; |
|
|
|
} |
|
|
|
|
|
|
|
function black (x) { |
|
|
|
return x < 64 |
|
|
|
function black(x) { |
|
|
|
return x < 64; |
|
|
|
} |
|
|
|
|
|
|
|
// Calculates "disorder" of the image. "Disorder" is the percentage of black pixels that have a
|
|
|
|
// non-black pixel below them. Minimizing this seems to be good enough metric for solving the slider.
|
|
|
|
function calculateDisorder (imgdata) { |
|
|
|
const a = imgdata.data |
|
|
|
const w = imgdata.width |
|
|
|
const h = imgdata.height |
|
|
|
const pic = [] |
|
|
|
const visited = [] |
|
|
|
function calculateDisorder(imgdata) { |
|
|
|
const a = imgdata.data; |
|
|
|
const w = imgdata.width; |
|
|
|
const h = imgdata.height; |
|
|
|
const pic = []; |
|
|
|
const visited = []; |
|
|
|
|
|
|
|
for (let c = 0; c < w * h; c++) { |
|
|
|
if (visited[c]) continue |
|
|
|
if (!black(a[c * 4])) continue |
|
|
|
if (visited[c]) continue; |
|
|
|
if (!black(a[c * 4])) continue; |
|
|
|
|
|
|
|
let blackCount = 0 |
|
|
|
const items = [] |
|
|
|
const toVisit = [c] |
|
|
|
let blackCount = 0; |
|
|
|
const items = []; |
|
|
|
const toVisit = [c]; |
|
|
|
while (toVisit.length > 0) { |
|
|
|
const cc = toVisit[toVisit.length - 1] |
|
|
|
toVisit.splice(toVisit.length - 1, 1) |
|
|
|
const cc = toVisit[toVisit.length - 1]; |
|
|
|
toVisit.splice(toVisit.length - 1, 1); |
|
|
|
|
|
|
|
if (visited[cc]) continue |
|
|
|
visited[cc] = 1 |
|
|
|
if (visited[cc]) continue; |
|
|
|
visited[cc] = 1; |
|
|
|
|
|
|
|
if (black(a[cc * 4])) { |
|
|
|
items.push(cc) |
|
|
|
items.push(cc); |
|
|
|
|
|
|
|
blackCount++ |
|
|
|
toVisit.push(cc + 1) |
|
|
|
toVisit.push(cc - 1) |
|
|
|
toVisit.push(cc + w) |
|
|
|
toVisit.push(cc - w) |
|
|
|
blackCount++; |
|
|
|
toVisit.push(cc + 1); |
|
|
|
toVisit.push(cc - 1); |
|
|
|
toVisit.push(cc + w); |
|
|
|
toVisit.push(cc - w); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (blackCount >= 24) { |
|
|
|
items.forEach(function (x) { |
|
|
|
pic[x] = 1 |
|
|
|
}) |
|
|
|
pic[x] = 1; |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
let res = 0 |
|
|
|
let total = 0 |
|
|
|
let res = 0; |
|
|
|
let total = 0; |
|
|
|
for (let c = 0; c < w * h - w; c++) { |
|
|
|
if (pic[c] !== pic[c + w]) res += 1 |
|
|
|
if (pic[c]) total += 1 |
|
|
|
if (pic[c] !== pic[c + w]) res += 1; |
|
|
|
if (pic[c]) total += 1; |
|
|
|
} |
|
|
|
|
|
|
|
return res / (total === 0 ? 1 : total) |
|
|
|
return res / (total === 0 ? 1 : total); |
|
|
|
} |
|
|
|
|
|
|
|
// returns ImageData from captcha's background image, foreground image, and offset (ranging from 0 to -50)
|
|
|
|
function imageFromCanvas (img, bg, off) { |
|
|
|
const h = img.height |
|
|
|
const w = img.width |
|
|
|
const th = 80 |
|
|
|
const ph = 0 |
|
|
|
const pw = 16 |
|
|
|
const scale = th / h |
|
|
|
function imageFromCanvas(img, bg, off) { |
|
|
|
const h = img.height; |
|
|
|
const w = img.width; |
|
|
|
const th = 80; |
|
|
|
const ph = 0; |
|
|
|
const pw = 16; |
|
|
|
const scale = th / h; |
|
|
|
|
|
|
|
const canvas = document.createElement('canvas') |
|
|
|
canvas.height = w * scale + pw * 2 |
|
|
|
canvas.width = th |
|
|
|
const canvas = document.createElement('canvas'); |
|
|
|
canvas.height = w * scale + pw * 2; |
|
|
|
canvas.width = th; |
|
|
|
|
|
|
|
const ctx = canvas.getContext('2d', { willReadFrequently: true }) |
|
|
|
const ctx = canvas.getContext('2d', { willReadFrequently: true }); |
|
|
|
|
|
|
|
ctx.fillStyle = 'rgb(238,238,238)' |
|
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height) |
|
|
|
ctx.fillStyle = 'rgb(238,238,238)'; |
|
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
|
|
ctx.translate(canvas.width / 2, canvas.height / 2) |
|
|
|
ctx.scale(-scale, scale) |
|
|
|
ctx.rotate((90 * Math.PI) / 180) |
|
|
|
ctx.translate(canvas.width / 2, canvas.height / 2); |
|
|
|
ctx.scale(-scale, scale); |
|
|
|
ctx.rotate((90 * Math.PI) / 180); |
|
|
|
|
|
|
|
const adf = 1 / 3 |
|
|
|
const adf = 1 / 3; |
|
|
|
|
|
|
|
const draw = function (off) { |
|
|
|
if (bg) { |
|
|
|
const border = 4 |
|
|
|
const border = 4; |
|
|
|
ctx.drawImage( |
|
|
|
bg, |
|
|
|
-off + border, |
|
|
@ -173,314 +173,314 @@ function imageFromCanvas (img, bg, off) { |
|
|
|
-h / 2, |
|
|
|
w - border * 2, |
|
|
|
h |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
ctx.drawImage(img, -w / 2, -h / 2, w, h) |
|
|
|
} |
|
|
|
ctx.drawImage(img, -w / 2, -h / 2, w, h); |
|
|
|
}; |
|
|
|
|
|
|
|
// if off is not specified and background image is present, try to figure out
|
|
|
|
// the best offset automatically; select the offset that has smallest value of
|
|
|
|
// calculateDisorder for the resulting image
|
|
|
|
if (bg && off == null) { |
|
|
|
let bestDisorder = 999 |
|
|
|
let bestImagedata = null |
|
|
|
let bestOff = -1 |
|
|
|
let bestDisorder = 999; |
|
|
|
let bestImagedata = null; |
|
|
|
let bestOff = -1; |
|
|
|
|
|
|
|
for (let off = 0; off >= -50; off--) { |
|
|
|
draw(off) |
|
|
|
draw(off); |
|
|
|
|
|
|
|
let imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height) |
|
|
|
const disorder = calculateDisorder(imgdata) |
|
|
|
let imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height); |
|
|
|
const disorder = calculateDisorder(imgdata); |
|
|
|
|
|
|
|
if (disorder < bestDisorder) { |
|
|
|
bestDisorder = disorder |
|
|
|
draw(off) |
|
|
|
imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height) |
|
|
|
bestImagedata = imgdata |
|
|
|
bestOff = off |
|
|
|
bestDisorder = disorder; |
|
|
|
draw(off); |
|
|
|
imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height); |
|
|
|
bestImagedata = imgdata; |
|
|
|
bestOff = off; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// not the best idea to do this here
|
|
|
|
setTimeout(function () { |
|
|
|
const bg = document.getElementById('t-bg') |
|
|
|
const slider = document.getElementById('t-slider') |
|
|
|
if (!bg || !slider) return |
|
|
|
|
|
|
|
slider.value = -bestOff * 2 |
|
|
|
bg.style.backgroundPositionX = bestOff + 'px' |
|
|
|
}, 1) |
|
|
|
draw(bestOff) |
|
|
|
return bestImagedata |
|
|
|
const bg = document.getElementById('t-bg'); |
|
|
|
const slider = document.getElementById('t-slider'); |
|
|
|
if (!bg || !slider) return; |
|
|
|
|
|
|
|
slider.value = -bestOff * 2; |
|
|
|
bg.style.backgroundPositionX = bestOff + 'px'; |
|
|
|
}, 1); |
|
|
|
draw(bestOff); |
|
|
|
return bestImagedata; |
|
|
|
} else { |
|
|
|
draw(off) |
|
|
|
return ctx.getImageData(0, 0, canvas.width, canvas.height) |
|
|
|
draw(off); |
|
|
|
return ctx.getImageData(0, 0, canvas.width, canvas.height); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// for debugging purposes
|
|
|
|
function imagedataToImage (imagedata) { |
|
|
|
const canvas = document.createElement('canvas') |
|
|
|
const ctx = canvas.getContext('2d') |
|
|
|
canvas.width = imagedata.width |
|
|
|
canvas.height = imagedata.height |
|
|
|
ctx.putImageData(imagedata, 0, 0) |
|
|
|
|
|
|
|
const image = new Image() |
|
|
|
image.src = canvas.toDataURL() |
|
|
|
return image |
|
|
|
function imagedataToImage(imagedata) { |
|
|
|
const canvas = document.createElement('canvas'); |
|
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
canvas.width = imagedata.width; |
|
|
|
canvas.height = imagedata.height; |
|
|
|
ctx.putImageData(imagedata, 0, 0); |
|
|
|
|
|
|
|
const image = new Image(); |
|
|
|
image.src = canvas.toDataURL(); |
|
|
|
return image; |
|
|
|
} |
|
|
|
|
|
|
|
async function predict (img, bg, off) { |
|
|
|
async function predict(img, bg, off) { |
|
|
|
if (!model) { |
|
|
|
model = await load() |
|
|
|
model = await load(); |
|
|
|
} |
|
|
|
const image = imageFromCanvas(img, bg, off) |
|
|
|
const labels = ccl.connectedComponentLabeling(image.data.map(e => +(e > 128)), image.width, image.height) |
|
|
|
const props = ccl.computeBounds(labels, image.width, image.height) |
|
|
|
const image = imageFromCanvas(img, bg, off); |
|
|
|
const labels = ccl.connectedComponentLabeling(image.data.map(e => +(e > 128)), image.width, image.height); |
|
|
|
const props = ccl.computeBounds(labels, image.width, image.height); |
|
|
|
|
|
|
|
const sortedByArea = Object.entries(props).sort((a, b) => a[1].area - b[1].area) |
|
|
|
const eightBiggest = sortedByArea.slice(-8) |
|
|
|
const filtered = new Float32Array(80 * 300) |
|
|
|
const sortedByArea = Object.entries(props).sort((a, b) => a[1].area - b[1].area); |
|
|
|
const eightBiggest = sortedByArea.slice(-8); |
|
|
|
const filtered = new Float32Array(80 * 300); |
|
|
|
|
|
|
|
// TODO: maybe centering?
|
|
|
|
for (const [label, region] of eightBiggest) { |
|
|
|
if ((region.maxRow - region.minRow) <= 20) { |
|
|
|
continue |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
for (let y = region.minRow; y < region.maxRow; ++y) { |
|
|
|
for (let x = region.minCol; y < region.maxCol; ++x) { |
|
|
|
if (labels[y * image.width + x] === label) { |
|
|
|
filtered[y * 300 + x] = 1 |
|
|
|
filtered[y * 300 + x] = 1; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const tensor = tf.tensor3d(filtered, [80, 300, 1], 'float32') |
|
|
|
const prediction = await model.predict(tensor.expandDims(0)).data() |
|
|
|
|
|
|
|
return createSequence(prediction) |
|
|
|
const tensor = tf.tensor3d(filtered, [80, 300, 1], 'float32'); |
|
|
|
const prediction = await model.predict(tensor.expandDims(0)).data(); |
|
|
|
|
|
|
|
return createSequence(prediction); |
|
|
|
} |
|
|
|
|
|
|
|
function createSequence (prediction) { |
|
|
|
const csl = charset.length |
|
|
|
const sequence = [] |
|
|
|
function createSequence(prediction) { |
|
|
|
const csl = charset.length; |
|
|
|
const sequence = []; |
|
|
|
|
|
|
|
// for each prediction
|
|
|
|
for (let pos = 0; pos < prediction.length; pos += csl) { |
|
|
|
// look at the probabilities for the 22 token characters
|
|
|
|
const preds = prediction.slice(pos, pos + csl) |
|
|
|
const max = Math.max(...preds) |
|
|
|
const preds = prediction.slice(pos, pos + csl); |
|
|
|
const max = Math.max(...preds); |
|
|
|
|
|
|
|
const seqElem = {} |
|
|
|
const seqElem = {}; |
|
|
|
|
|
|
|
for (let i = 0; i < csl; i++) { |
|
|
|
const p = preds[i] / max // normalize probability
|
|
|
|
const c = charset[i + 1] |
|
|
|
const p = preds[i] / max; // normalize probability
|
|
|
|
const c = charset[i + 1]; |
|
|
|
|
|
|
|
if (p >= 0.05) { // if it's probable enough
|
|
|
|
seqElem[c || ''] = p // save its probability, to give alternative solutions
|
|
|
|
seqElem[c || ''] = p; // save its probability, to give alternative solutions
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
sequence.push(seqElem) |
|
|
|
sequence.push(seqElem); |
|
|
|
} |
|
|
|
|
|
|
|
return sequence |
|
|
|
return sequence; |
|
|
|
} |
|
|
|
|
|
|
|
function postprocess (sequence, overrides) { |
|
|
|
const csl = charset.length |
|
|
|
let possibilities = [{ sequence: [] }] |
|
|
|
function postprocess(sequence, overrides) { |
|
|
|
const csl = charset.length; |
|
|
|
let possibilities = [{ sequence: [] }]; |
|
|
|
|
|
|
|
sequence.forEach(function (e, i) { |
|
|
|
let additions |
|
|
|
let additions; |
|
|
|
if (overrides && overrides[i] !== undefined) { |
|
|
|
additions = [{ sym: overrides[i], off: i, conf: 1 }] |
|
|
|
additions = [{ sym: overrides[i], off: i, conf: 1 }]; |
|
|
|
} else { |
|
|
|
additions = Object.keys(e).map(function (sym) { |
|
|
|
return { sym, off: i, conf: e[sym] } |
|
|
|
}) |
|
|
|
return { sym, off: i, conf: e[sym] }; |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
if (additions.length === 1 && additions[0].sym === '') return |
|
|
|
if (additions.length === 1 && additions[0].sym === '') return; |
|
|
|
|
|
|
|
const oldpos = possibilities |
|
|
|
possibilities = [] |
|
|
|
const oldpos = possibilities; |
|
|
|
possibilities = []; |
|
|
|
oldpos.forEach(function (possibility) { |
|
|
|
additions.forEach(function (a) { |
|
|
|
const seq = [...possibility.sequence] |
|
|
|
if (a.sym !== '') seq.push([a.sym, a.off, a.conf]) |
|
|
|
const seq = [...possibility.sequence]; |
|
|
|
if (a.sym !== '') seq.push([a.sym, a.off, a.conf]); |
|
|
|
|
|
|
|
const obj = { |
|
|
|
sequence: seq |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
possibilities.push(obj) |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
possibilities.push(obj); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
const res = {} |
|
|
|
const res = {}; |
|
|
|
possibilities.forEach(function (p) { |
|
|
|
let line = '' |
|
|
|
let lastSym |
|
|
|
let lastOff = -1 |
|
|
|
let count = 0 |
|
|
|
let prob = 0 |
|
|
|
let line = ''; |
|
|
|
let lastSym; |
|
|
|
let lastOff = -1; |
|
|
|
let count = 0; |
|
|
|
let prob = 0; |
|
|
|
|
|
|
|
p.sequence.forEach(function (e) { |
|
|
|
const sym = e[0] |
|
|
|
const off = e[1] |
|
|
|
const conf = e[2] |
|
|
|
const sym = e[0]; |
|
|
|
const off = e[1]; |
|
|
|
const conf = e[2]; |
|
|
|
|
|
|
|
if (sym === lastSym && lastOff + 2 >= off) { |
|
|
|
return |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
line += sym |
|
|
|
line += sym; |
|
|
|
|
|
|
|
lastSym = sym |
|
|
|
lastOff = off |
|
|
|
prob += conf |
|
|
|
count++ |
|
|
|
}) |
|
|
|
lastSym = sym; |
|
|
|
lastOff = off; |
|
|
|
prob += conf; |
|
|
|
count++; |
|
|
|
}); |
|
|
|
|
|
|
|
if (count > 0) prob /= count |
|
|
|
if (count > 0) prob /= count; |
|
|
|
|
|
|
|
if (prob > res[line] || !res[line]) { |
|
|
|
res[line] = prob |
|
|
|
res[line] = prob; |
|
|
|
} |
|
|
|
}) |
|
|
|
}); |
|
|
|
|
|
|
|
let keys = Object.keys(res).sort(function (a, b) { |
|
|
|
return res[a] < res[b] |
|
|
|
}) |
|
|
|
return res[a] < res[b]; |
|
|
|
}); |
|
|
|
const keysFitting = keys.filter(function (x) { |
|
|
|
return x.length === 5 || x.length === 6 |
|
|
|
}) |
|
|
|
if (keysFitting.length > 0) keys = keysFitting |
|
|
|
return x.length === 5 || x.length === 6; |
|
|
|
}); |
|
|
|
if (keysFitting.length > 0) keys = keysFitting; |
|
|
|
|
|
|
|
return keys.map(function (x) { |
|
|
|
return { seq: x, prob: res[x] } |
|
|
|
}) |
|
|
|
return { seq: x, prob: res[x] }; |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
async function imageFromUri (uri) { |
|
|
|
async function imageFromUri(uri) { |
|
|
|
if (uri.startsWith('url("')) { |
|
|
|
uri = uri.substr(5, uri.length - 7) |
|
|
|
uri = uri.substr(5, uri.length - 7); |
|
|
|
} |
|
|
|
// eslint-disable-next-line camelcase
|
|
|
|
if (execution_mode !== 'test' && !uri.startsWith('data:')) { |
|
|
|
return null |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
const img = new Image() |
|
|
|
await new Promise((r) => (img.onload = r), (img.src = uri)) |
|
|
|
return img |
|
|
|
const img = new Image(); |
|
|
|
await new Promise((r) => (img.onload = r), (img.src = uri)); |
|
|
|
return img; |
|
|
|
} |
|
|
|
|
|
|
|
async function predictUri (uri, uribg, bgoff) { |
|
|
|
const img = await imageFromUri(uri) |
|
|
|
const bg = uribg ? await imageFromUri(uribg) : null |
|
|
|
const off = bgoff ? parseInt(bgoff) : null |
|
|
|
async function predictUri(uri, uribg, bgoff) { |
|
|
|
const img = await imageFromUri(uri); |
|
|
|
const bg = uribg ? await imageFromUri(uribg) : null; |
|
|
|
const off = bgoff ? parseInt(bgoff) : null; |
|
|
|
|
|
|
|
return await predict(img, bg, off) |
|
|
|
return await predict(img, bg, off); |
|
|
|
} |
|
|
|
|
|
|
|
const solveButton = document.createElement('input') |
|
|
|
solveButton.id = 't-auto-solve' |
|
|
|
solveButton.value = 'Solve' |
|
|
|
solveButton.type = 'button' |
|
|
|
solveButton.style.fontSize = '11px' |
|
|
|
solveButton.style.padding = '0 2px' |
|
|
|
solveButton.style.margin = '0px 0px 0px 6px' |
|
|
|
solveButton.style.height = '18px' |
|
|
|
const solveButton = document.createElement('input'); |
|
|
|
solveButton.id = 't-auto-solve'; |
|
|
|
solveButton.value = 'Solve'; |
|
|
|
solveButton.type = 'button'; |
|
|
|
solveButton.style.fontSize = '11px'; |
|
|
|
solveButton.style.padding = '0 2px'; |
|
|
|
solveButton.style.margin = '0px 0px 0px 6px'; |
|
|
|
solveButton.style.height = '18px'; |
|
|
|
solveButton.onclick = async function () { |
|
|
|
solve(true) |
|
|
|
} |
|
|
|
solve(true); |
|
|
|
}; |
|
|
|
|
|
|
|
const altsDiv = document.createElement('div') |
|
|
|
altsDiv.id = 't-auto-options' |
|
|
|
altsDiv.style.margin = '0' |
|
|
|
altsDiv.style.padding = '0' |
|
|
|
const altsDiv = document.createElement('div'); |
|
|
|
altsDiv.id = 't-auto-options'; |
|
|
|
altsDiv.style.margin = '0'; |
|
|
|
altsDiv.style.padding = '0'; |
|
|
|
|
|
|
|
let storedPalceholder |
|
|
|
let storedPalceholder; |
|
|
|
|
|
|
|
let overrides = {} |
|
|
|
let overrides = {}; |
|
|
|
|
|
|
|
function placeAfter (elem, sibling) { |
|
|
|
function placeAfter(elem, sibling) { |
|
|
|
if (elem.parentElement !== sibling.parentElement) { |
|
|
|
setTimeout(function () { |
|
|
|
sibling.parentElement.insertBefore(elem, sibling.nextElementSibling) |
|
|
|
}, 1) |
|
|
|
sibling.parentElement.insertBefore(elem, sibling.nextElementSibling); |
|
|
|
}, 1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
let previousText = null |
|
|
|
async function solve (force) { |
|
|
|
const resp = document.getElementById('t-resp') |
|
|
|
if (!resp) return |
|
|
|
let previousText = null; |
|
|
|
async function solve(force) { |
|
|
|
const resp = document.getElementById('t-resp'); |
|
|
|
if (!resp) return; |
|
|
|
|
|
|
|
const bg = document.getElementById('t-bg') |
|
|
|
if (!bg) return |
|
|
|
const bg = document.getElementById('t-bg'); |
|
|
|
if (!bg) return; |
|
|
|
|
|
|
|
const fg = document.getElementById('t-fg') |
|
|
|
if (!fg) return |
|
|
|
const fg = document.getElementById('t-fg'); |
|
|
|
if (!fg) return; |
|
|
|
|
|
|
|
const help = document.getElementById('t-help') |
|
|
|
if (!help) return |
|
|
|
const help = document.getElementById('t-help'); |
|
|
|
if (!help) return; |
|
|
|
|
|
|
|
await backendloaded |
|
|
|
await backendloaded; |
|
|
|
|
|
|
|
placeAfter(solveButton, resp) |
|
|
|
placeAfter(altsDiv, help) |
|
|
|
placeAfter(solveButton, resp); |
|
|
|
placeAfter(altsDiv, help); |
|
|
|
|
|
|
|
// palememe
|
|
|
|
setTimeout(function () { |
|
|
|
toggle(solveButton, bg.style.backgroundImage) |
|
|
|
}, 1) |
|
|
|
toggle(solveButton, bg.style.backgroundImage); |
|
|
|
}, 1); |
|
|
|
|
|
|
|
const text = fg.style.backgroundImage |
|
|
|
const text = fg.style.backgroundImage; |
|
|
|
if (!text) { |
|
|
|
altsDiv.innerHTML = '' |
|
|
|
return |
|
|
|
altsDiv.innerHTML = ''; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (text === previousText && !force) return |
|
|
|
previousText = text |
|
|
|
if (text === previousText && !force) return; |
|
|
|
previousText = text; |
|
|
|
|
|
|
|
altsDiv.innerHTML = '' |
|
|
|
if (!storedPalceholder) storedPalceholder = resp.placeholder |
|
|
|
resp.placeholder = 'solving captcha...' |
|
|
|
altsDiv.innerHTML = ''; |
|
|
|
if (!storedPalceholder) storedPalceholder = resp.placeholder; |
|
|
|
resp.placeholder = 'solving captcha...'; |
|
|
|
|
|
|
|
overrides = {} |
|
|
|
overrides = {}; |
|
|
|
|
|
|
|
const sequence = await predictUri( |
|
|
|
text, |
|
|
|
bg.style.backgroundImage, |
|
|
|
force ? bg.style.backgroundPositionX : null |
|
|
|
) |
|
|
|
const opts = postprocess(sequence) |
|
|
|
resp.placeholder = storedPalceholder |
|
|
|
); |
|
|
|
const opts = postprocess(sequence); |
|
|
|
resp.placeholder = storedPalceholder; |
|
|
|
|
|
|
|
showOpts(opts) |
|
|
|
showOpts(opts); |
|
|
|
} |
|
|
|
|
|
|
|
function showOpts (opts) { |
|
|
|
const resp = document.getElementById('t-resp') |
|
|
|
if (!resp) return |
|
|
|
function showOpts(opts) { |
|
|
|
const resp = document.getElementById('t-resp'); |
|
|
|
if (!resp) return; |
|
|
|
|
|
|
|
altsDiv.innerHTML = '' |
|
|
|
altsDiv.innerHTML = ''; |
|
|
|
|
|
|
|
if (opts.length === 0) { |
|
|
|
resp.value = '' |
|
|
|
return |
|
|
|
resp.value = ''; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
resp.value = opts[0].seq |
|
|
|
resp.value = opts[0].seq; |
|
|
|
|
|
|
|
// for now don't display options since it seems more difficult to pick than type the whole thing
|
|
|
|
// eslint-disable-next-line no-constant-condition, no-empty
|
|
|
@ -490,11 +490,11 @@ function showOpts (opts) { |
|
|
|
} |
|
|
|
|
|
|
|
const observer = new MutationObserver(async function (mutationsList, observer) { |
|
|
|
solve(false) |
|
|
|
}) |
|
|
|
window.solve = solve |
|
|
|
solve(false); |
|
|
|
}); |
|
|
|
window.solve = solve; |
|
|
|
observer.observe(document.body, { |
|
|
|
attributes: true, |
|
|
|
childList: true, |
|
|
|
subtree: true |
|
|
|
}) |
|
|
|
}); |
|
|
|