Распознаю штрихкод…
// 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 `
Распознаю штрихкод…