Обновить t1.js
This commit is contained in:
parent
5158e84272
commit
6a9cec78be
203
t1.js
203
t1.js
@ -1 +1,202 @@
|
|||||||
let test = "string";
|
// t1.js — удалённый скрипт для Scriptable Remote Loader
|
||||||
|
// Сканирование штрихкодов/QR по фото + история
|
||||||
|
|
||||||
|
async function __main(ctx) { // точка входа для бутлоадера
|
||||||
|
const menu = new Alert();
|
||||||
|
menu.title = "Сканер (фото) + история";
|
||||||
|
menu.addAction("Сканировать");
|
||||||
|
menu.addAction("История");
|
||||||
|
menu.addDestructiveAction("Очистить историю");
|
||||||
|
menu.addCancelAction("Отмена");
|
||||||
|
const c = await menu.presentSheet();
|
||||||
|
if (c === 0) return scanFromCamera(ctx);
|
||||||
|
if (c === 1) return showHistory();
|
||||||
|
if (c === 2) return clearHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Настройки/хранилище ---
|
||||||
|
const FILE_NAME = "scriptable_scans.json";
|
||||||
|
const USE_ICLOUD = false;
|
||||||
|
const fm = USE_ICLOUD ? FileManager.iCloud() : FileManager.local();
|
||||||
|
const storePath = fm.joinPath(fm.documentsDirectory(), FILE_NAME);
|
||||||
|
|
||||||
|
function loadHistory() {
|
||||||
|
try {
|
||||||
|
if (!fm.fileExists(storePath)) return [];
|
||||||
|
return JSON.parse(fm.readString(storePath)) || [];
|
||||||
|
} catch (e) { console.error(e); return []; }
|
||||||
|
}
|
||||||
|
function saveHistory(arr) {
|
||||||
|
try { fm.writeString(storePath, JSON.stringify(arr, null, 2)); }
|
||||||
|
catch (e) { console.error(e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Основные функции ---
|
||||||
|
async function scanFromCamera(ctx) {
|
||||||
|
try {
|
||||||
|
const img = await Photos.fromCamera(); // системный запрос на доступ к камере тут
|
||||||
|
const base64 = Data.fromJPEG(img).toBase64String(); // JPEG — лучше для 1D
|
||||||
|
const wv = new WebView();
|
||||||
|
|
||||||
|
wv.shouldAllowRequest = req => {
|
||||||
|
const u = req.url || "";
|
||||||
|
if (u.startsWith("scriptable://decoded")) {
|
||||||
|
const q = parseQS(u);
|
||||||
|
const text = q.text || "";
|
||||||
|
const format = q.format || "UNKNOWN";
|
||||||
|
if (text) {
|
||||||
|
const h = loadHistory();
|
||||||
|
h.unshift({ text, format, ts: new Date().toISOString() });
|
||||||
|
saveHistory(h);
|
||||||
|
notify(ctx, "Сохранено", `${format}: ${text}`);
|
||||||
|
} else {
|
||||||
|
notify(ctx, "Не найдено", "Код распознать не удалось");
|
||||||
|
}
|
||||||
|
return false; // не покидать WebView
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
await wv.loadHTML(decoderHTML(base64));
|
||||||
|
await wv.present(true);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
await notify(ctx, "Ошибка", String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showHistory() {
|
||||||
|
const history = loadHistory();
|
||||||
|
const table = new UITable();
|
||||||
|
table.showSeparators = true;
|
||||||
|
|
||||||
|
const hdr = new UITableRow();
|
||||||
|
const c = hdr.addText("История", `${history.length} записей`);
|
||||||
|
c.titleFont = Font.boldSystemFont(16);
|
||||||
|
c.subtitleFont = Font.systemFont(12);
|
||||||
|
table.addRow(hdr);
|
||||||
|
|
||||||
|
if (!history.length) {
|
||||||
|
const r = new UITableRow();
|
||||||
|
r.addText("Пусто", "Сканируйте код через «Сканировать».");
|
||||||
|
table.addRow(r);
|
||||||
|
await table.present(); return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const it of history) {
|
||||||
|
const r = new UITableRow();
|
||||||
|
r.onSelect = async () => { Pasteboard.copy(it.text); await notify(null, "Скопировано", it.text); };
|
||||||
|
r.addText(it.text, `${it.format || "CODE"} · ${new Date(it.ts).toLocaleString()}`);
|
||||||
|
table.addRow(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
await table.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearHistory() {
|
||||||
|
const x = new Alert();
|
||||||
|
x.title = "Очистить историю?";
|
||||||
|
x.addDestructiveAction("Очистить");
|
||||||
|
x.addCancelAction("Отмена");
|
||||||
|
if (await x.presentAlert() === 0) {
|
||||||
|
saveHistory([]);
|
||||||
|
await notify(null, "Готово", "История очищена");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Вспомогательные ---
|
||||||
|
function parseQS(url) {
|
||||||
|
const out = {}; const i = url.indexOf("?"); if (i < 0) return out;
|
||||||
|
for (const p of url.slice(i + 1).split("&")) {
|
||||||
|
const [k, v] = p.split("=");
|
||||||
|
out[decodeURIComponent(k || "")] = decodeURIComponent(v || "");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function notify(ctx, title, body) {
|
||||||
|
try {
|
||||||
|
if (ctx && typeof ctx.notify === "function") return ctx.notify(title, body);
|
||||||
|
const n = new Notification(); n.title = title; n.body = body; await n.schedule();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HTML с ZXing: распознаём 1D + QR, несколько попыток и повороты ---
|
||||||
|
function decoderHTML(b64) {
|
||||||
|
return `<!doctype html><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Decode</title>
|
||||||
|
<style>
|
||||||
|
body{margin:0;background:#111;color:#eee;font-family:-apple-system,system-ui}
|
||||||
|
main{padding:16px}
|
||||||
|
pre{white-space:pre-wrap}
|
||||||
|
canvas{max-width:100%;display:block;border:1px solid #333}
|
||||||
|
</style>
|
||||||
|
<main>
|
||||||
|
<p>Распознаю штрихкод…</p>
|
||||||
|
<canvas id="cv"></canvas>
|
||||||
|
<pre id="out"></pre>
|
||||||
|
</main>
|
||||||
|
<script src="https://unpkg.com/@zxing/library@latest"></script>
|
||||||
|
<script>
|
||||||
|
(async () => {
|
||||||
|
const img = new Image(); img.src = "data:image/jpeg;base64,${b64}";
|
||||||
|
await new Promise(r => img.onload = r);
|
||||||
|
|
||||||
|
const cv = document.getElementById('cv');
|
||||||
|
const ctx = cv.getContext('2d');
|
||||||
|
|
||||||
|
// Масштабируем до разумного предела
|
||||||
|
const maxSide = 1920;
|
||||||
|
let w = img.naturalWidth, h = img.naturalHeight;
|
||||||
|
const k = Math.max(w,h) > maxSide ? maxSide/Math.max(w,h) : 1;
|
||||||
|
w = Math.round(w*k); h = Math.round(h*k);
|
||||||
|
cv.width = w; cv.height = h; ctx.drawImage(img,0,0,w,h);
|
||||||
|
|
||||||
|
const formats = [
|
||||||
|
ZXing.BarcodeFormat.EAN_13, ZXing.BarcodeFormat.EAN_8,
|
||||||
|
ZXing.BarcodeFormat.UPC_A, ZXing.BarcodeFormat.UPC_E,
|
||||||
|
ZXing.BarcodeFormat.CODE_128, ZXing.BarcodeFormat.CODE_39,
|
||||||
|
ZXing.BarcodeFormat.ITF, ZXing.BarcodeFormat.CODABAR,
|
||||||
|
ZXing.BarcodeFormat.QR_CODE
|
||||||
|
];
|
||||||
|
const hints = new Map();
|
||||||
|
hints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, formats);
|
||||||
|
hints.set(ZXing.DecodeHintType.TRY_HARDER, true);
|
||||||
|
const reader = new ZXing.MultiFormatReader(); reader.setHints(hints);
|
||||||
|
|
||||||
|
function bitmap(global){
|
||||||
|
const {data,width,height} = ctx.getImageData(0,0,cv.width,cv.height);
|
||||||
|
const ls = new ZXing.RGBLuminanceSource(data, width, height);
|
||||||
|
const bin = global ? new ZXing.GlobalHistogramBinarizer(ls) : new ZXing.HybridBinarizer(ls);
|
||||||
|
return new ZXing.BinaryBitmap(bin);
|
||||||
|
}
|
||||||
|
async function rotate90(){
|
||||||
|
const t = document.createElement('canvas'), tctx = t.getContext('2d');
|
||||||
|
t.width = cv.height; t.height = cv.width;
|
||||||
|
tctx.translate(t.width/2, t.height/2); tctx.rotate(Math.PI/2);
|
||||||
|
tctx.drawImage(cv, -cv.width/2, -cv.height/2);
|
||||||
|
cv.width = t.width; cv.height = t.height; ctx.drawImage(t,0,0);
|
||||||
|
}
|
||||||
|
function tryDecode(){
|
||||||
|
try { const r = reader.decode(bitmap(false));
|
||||||
|
return { text: r.getText ? r.getText() : (r.text || ""),
|
||||||
|
format: r.getBarcodeFormat ? String(r.getBarcodeFormat()) : (r.barcodeFormat || "CODE") };
|
||||||
|
} catch(e){ try { const r = reader.decode(bitmap(true));
|
||||||
|
return { text: r.getText ? r.getText() : (r.text || ""),
|
||||||
|
format: r.getBarcodeFormat ? String(r.getBarcodeFormat()) : (r.barcodeFormat || "CODE") };
|
||||||
|
} catch(e2){ return null; } }
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = null;
|
||||||
|
for (let i=0;i<4 && !res;i++){ res = tryDecode(); if(!res) await rotate90(); }
|
||||||
|
|
||||||
|
if (res && res.text) {
|
||||||
|
document.getElementById('out').textContent = res.format + ": " + res.text;
|
||||||
|
location.href = "scriptable://decoded?text=" + encodeURIComponent(res.text) + "&format=" + encodeURIComponent(res.format);
|
||||||
|
} else {
|
||||||
|
document.getElementById('out').textContent = "Не удалось распознать код";
|
||||||
|
location.href = "scriptable://decoded?text=&format=";
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>`;
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user