jsios/t1.js
2025-08-14 05:49:36 +00:00

203 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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>`;
}