Helper for AlloggiatiWeb

Helpers for Alloggiati Web.

// ==UserScript==
// @name        Helper for AlloggiatiWeb
// @namespace   marcuson
// @description Helpers for Alloggiati Web.
// @match       https://alloggiatiweb.poliziadistato.it/AlloggiatiWeb/SecurityCODS.aspx
// @version     1.2.1
// @author      marcuson
// @license     GPL-3.0-or-later
// @supportURL  https://github.com/marcuson/alloggiatiweb-helpers/issues
// @homepageURL https://github.com/marcuson/alloggiatiweb-helpers
// @require     https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@2,npm/@violentmonkey/[email protected]
// @grant       GM.addStyle
// @grant       GM.getValue
// @grant       GM.setValue
// ==/UserScript==

(function () {
'use strict';

function downloadObjectAsJson(exportObj, exportName) {
  const dataStr = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(exportObj));
  const downloadAnchorNode = document.createElement('a');
  downloadAnchorNode.setAttribute('href', dataStr);
  downloadAnchorNode.setAttribute('download', exportName + '.json');
  document.body.appendChild(downloadAnchorNode); // required for firefox
  downloadAnchorNode.click();
  downloadAnchorNode.remove();
}

async function askFileToRead() {
  const input = document.createElement('input');
  input.type = 'file';
  const prom = new Promise((res, rej) => {
    input.onchange = () => {
      const file = input.files[0];
      const reader = new FileReader();
      reader.readAsText(file, 'UTF-8');
      reader.onload = readerEvent => {
        const content = readerEvent.target.result;
        res(content);
      };
      reader.onerror = err => {
        rej(err);
      };
    };
  });
  input.setAttribute('style', 'display:none;');
  document.body.appendChild(input); // required for firefox
  input.click();
  const res = await prom;
  input.remove();
  return res;
}

async function getOpts() {
  return await GM.getValue('options', undefined);
}
async function saveOpts(opts) {
  await GM.setValue('options', opts);
}
function exportOpts(opts) {
  downloadObjectAsJson(opts, 'alloggiatiweb-options');
}
async function importOpts() {
  const optsStr = await askFileToRead();
  const opts = JSON.parse(optsStr);
  return opts;
}

var css_248z = "";

var styles = {"code":"style-module_code__ds1Ud","error":"style-module_error__ABgWN","warning":"style-module_warning__nASzn","success":"style-module_success__ZG5Xf"};
var stylesheet=".style-module_code__ds1Ud{max-width:100px}.style-module_error__ABgWN{color:red}.style-module_warning__nASzn{color:orange}.style-module_success__ZG5Xf{color:green}";

const CODE_ROWS = 4;
const CODE_COLS = 4;
let panel = undefined;
let panelOptions = undefined;
let ocrSpaceApiKeyInput;
// FIXME: Add init based on rows and cols constants
const codeInputs = [[], [], [], []];
async function showOptions() {
  if (panel) {
    return;
  }
  panelOptions = await getOpts();
  if (!panelOptions) {
    // FIXME: Add init based on rows and cols constants
    panelOptions = {
      codes: [['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']],
      ocrSpaceApiKey: ''
    };
  }
  ocrSpaceApiKeyInput = VM.m(VM.h("input", {
    type: "text",
    name: "ocrspaceapikey",
    onchange: onOcrSpaceApiKeyChange,
    value: panelOptions.ocrSpaceApiKey
  }));
  const codeInputsFlat = [];
  for (let x = 0; x < CODE_COLS; x++) {
    codeInputsFlat.push(VM.h("br", null));
    for (let y = 0; y < CODE_ROWS; y++) {
      codeInputs[x][y] = VM.m(VM.h("input", {
        class: styles.code,
        type: "text",
        name: `code_${x}_${y}`,
        onchange: onCodeChange,
        value: panelOptions.codes[x][y]
      }));
      codeInputsFlat.push(codeInputs[x][y]);
    }
  }
  panel = VM.getPanel({
    content: VM.m(VM.h("div", null, VM.h("form", null, VM.h("label", {
      for: "ocrspaceapikey"
    }, "OCR space API KEY"), ocrSpaceApiKeyInput, VM.h("br", null), VM.h("label", {
      for: "codes"
    }, "Codes"), codeInputsFlat), VM.h("button", {
      onclick: onSaveBtnClick
    }, "Save"), VM.h("button", {
      onclick: onCancelBtnClick
    }, "Cancel"), VM.h("hr", null), VM.h("button", {
      onclick: onExportBtnClick
    }, "Export"), VM.h("button", {
      onclick: onImportBtnClick
    }, "Import"))),
    style: [css_248z, stylesheet].join('\n')
  });
  writeOptionsIntoInputs();
  panel.wrapper.style.top = '100px';
  panel.wrapper.style.left = '100px';
  panel.show();
}
function onOcrSpaceApiKeyChange(e) {
  e.preventDefault();
  const inp = e.currentTarget;
  panelOptions.ocrSpaceApiKey = inp.value;
}
function onCodeChange(e) {
  e.preventDefault();
  const inp = e.currentTarget;
  const x = inp.name.substring('code_'.length, 'code_'.length + 1);
  const y = inp.name.substring('code_X_'.length, 'code_X_'.length + 1);
  panelOptions.codes[x][y] = inp.value;
}
function onExportBtnClick(e) {
  e.preventDefault();
  exportOpts(panelOptions);
}
async function onImportBtnClick(e) {
  e.preventDefault();
  panelOptions = await importOpts();
  writeOptionsIntoInputs();
}
function writeOptionsIntoInputs() {
  for (let x = 0; x < CODE_COLS; x++) {
    for (let y = 0; y < CODE_ROWS; y++) {
      codeInputs[x][y].value = panelOptions.codes[x][y];
    }
  }
  ocrSpaceApiKeyInput.value = panelOptions.ocrSpaceApiKey;
}
function onCancelBtnClick(e) {
  e.preventDefault();
  closePanel();
}
function closePanel() {
  panel.hide();
  panel.dispose();
  panel = undefined;
  panelOptions = undefined;
}
async function onSaveBtnClick(e) {
  e.preventDefault();
  console.debug('Options to save:', panelOptions);
  await saveOpts(panelOptions);
  closePanel();
}

function _extends() {
  _extends = Object.assign ? Object.assign.bind() : function (target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];
      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }
    return target;
  };
  return _extends.apply(this, arguments);
}

function toast(type, msg, options) {
  const clz = styles[type];
  const st = [(options == null ? void 0 : options.style) || '', css_248z, stylesheet].join('\n');
  return VM.showToast(VM.h("div", {
    class: clz
  }, VM.h("p", null, msg)), _extends({}, options, {
    style: st
  }));
}

const BOXES_LENGTH = 8;
const BOXES_LENGTH_HALF = BOXES_LENGTH / 2;

function getBase64ImagePngFromCanvas(canvas) {
  const dataURL = canvas.toDataURL('image/png');
  return dataURL;
}
function getCanvasFromImg(imgElem) {
  const canvas = document.createElement('canvas');
  canvas.width = imgElem.naturalWidth;
  canvas.height = imgElem.naturalHeight;
  canvas.getContext('2d').drawImage(imgElem, 0, 0);
  return canvas;
}
async function ocr(apiKey, base64Img, ocrEngine) {
  const fd = new FormData();
  fd.append('base64Image', base64Img);
  fd.append('language', 'ita');
  fd.append('scale', 'true');
  fd.append('isTable', 'true');
  fd.append('OCREngine', ocrEngine || '2');
  return await fetch('https://api.ocr.space/parse/image', {
    method: 'POST',
    headers: {
      apikey: apiKey
    },
    body: fd
  }).then(res => {
    if (!res.ok) {
      throw new Error(`Error during OCR API call: status code ${res.status}.`);
    }
    return res.json();
  }).then(json => json.ParsedResults && json.ParsedResults.length > 0 ? json.ParsedResults[0] : {
    ParsedText: ''
  }).then(r => r.ParsedText);
}

let showOptionsBtn;
let showCorrectCodeBtn;
let correctCodeMsgDiv;
let msgDiv;
function augmentCodesPage() {
  console.info('CODS page of Alloggiati Web detected, inject to DOM');
  addMsgs();
  addButtons();
}
function addButtons() {
  showCorrectCodeBtn = VM.m(VM.h("button", {
    class: "btn btn-primary btn-block center",
    style: "width:70%;",
    onclick: onShowCorrectCodeBtnClick
  }, "Get correct code"));
  showOptionsBtn = VM.m(VM.h("button", {
    class: "btn btn-primary btn-block center",
    style: "width:70%;",
    onclick: onShowOptionsBtnClick
  }, "Options"));
  const title = document.querySelector('#Titolo');
  title.after(showCorrectCodeBtn);
  title.after(showOptionsBtn);
}
function addMsgs() {
  msgDiv = VM.m(VM.h("div", null));
  correctCodeMsgDiv = VM.m(VM.h("div", null));
  const title = document.querySelector('#Titolo');
  title.after(correctCodeMsgDiv);
  title.after(msgDiv);
}
async function onShowCorrectCodeBtnClick(e) {
  console.debug('Show correct code btn clicked');
  e.preventDefault();
  await showCorrectCode();
}
function onShowOptionsBtnClick(e) {
  console.debug('Show options btn clicked');
  e.preventDefault();
  showOptions();
}
function showMsg(msg, divEl) {
  const actualPar = divEl || msgDiv;
  actualPar.textContent = msg;
}
function getEmptyBoxes() {
  const boxes = document.querySelectorAll('.Seccasella.Secp0');
  console.debug('Found boxes:', boxes);
  if (boxes.length !== BOXES_LENGTH) {
    throw new Error(`Boxes is not of length ${BOXES_LENGTH}!`);
  }
  const emptyBoxes = [];
  boxes.forEach((x, i) => {
    if (x.textContent === 'X') {
      return;
    }
    emptyBoxes.push({
      elem: x,
      boxGroupIdx: Math.floor(i / BOXES_LENGTH_HALF),
      groupElementIdx: i % BOXES_LENGTH_HALF
    });
  });
  return emptyBoxes;
}
async function getGroupIds(options) {
  console.debug('Get groups indexes as base64 image');
  const groupsB64 = getBase64ImagePngFromCanvas(getCanvasFromImg(document.querySelector('img.SecGruppi.pointer')));
  const groupsText = await ocr(options.ocrSpaceApiKey, groupsB64);
  console.debug('Parsed text from groups indexes is:', groupsText);
  return groupsText.toLowerCase().replace(/gruppo/g, '').replace(/\r\n/g, ' ').replace(/\s+/g, ' ').trim().split(' ').map(x => parseInt(x));
}
function getCorrectCode(options, groupIds, emptyBoxes) {
  const codesFlat = options.codes.flatMap(x => x);
  return emptyBoxes.map((x, i) => {
    const codeId = groupIds[x.boxGroupIdx];
    const code = codesFlat[codeId - 1];
    console.debug(`Code for emptyBox with index ${i} (codeId ${codeId}) is ${code}`);
    const digit = code.charAt(x.groupElementIdx);
    console.debug(`Digit for index ${i} is ${digit}`);
    return digit;
  });
}
async function showCorrectCode() {
  const options = await getOpts();
  if (!options) {
    toast('error', 'Configure options first!');
    return;
  }
  try {
    showMsg('Getting group ids...');
    const groupIds = await getGroupIds(options);
    console.info('Group ids are:', groupIds);
    showMsg('Getting empty boxes...');
    const emptyBoxes = getEmptyBoxes();
    console.info('Empty boxes info:', emptyBoxes);
    showMsg('Getting correct code...');
    const correctCode = getCorrectCode(options, groupIds, emptyBoxes);
    console.info('Correct code is:', correctCode);
    showMsg(`Correct code is: ${correctCode.join('')}`, correctCodeMsgDiv);
  } catch (e) {
    /** @type {Error} */
    const err = e;
    showMsg('Error: ' + err.message);
    throw e;
  }
}

// import CSS
document.head.append(VM.m(VM.h("style", null, css_248z)));
document.head.append(VM.m(VM.h("style", null, stylesheet)));

// init app
augmentCodesPage();

})();