Обновить 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