Browse Source

formatting

master
coomdev 9 months ago
parent
commit
a1d44a8443
  1. 4
      .eslintrc.json
  2. 520
      src/main.js

4
.eslintrc.json

@ -10,6 +10,8 @@
}, },
"rules": { "rules": {
"no-undef": ["off"], "no-undef": ["off"],
"no-unused-vars": "off" "no-unused-vars": "off",
"semi": "off",
"space-before-function-paren": "off"
} }
} }

520
src/main.js

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

Loading…
Cancel
Save