// FOTOHRA v2.4 // Oprava tlačítka: používá skutečný HTTPS odkaz, který zachytí Scriptable // přes WebView.shouldAllowRequest a otevře nativně v Safari nebo Opeře. const THREAD_URL = "https://www.bike-forum.cz/forum/fotohra-xxvi"; const DEADLINE_DAYS = 14; const fm = FileManager.local(); const appDir = fm.joinPath(fm.documentsDirectory(), "Fotohra"); const prefsPath = fm.joinPath(appDir, "prefs.json"); if (!fm.fileExists(appDir)) { fm.createDirectory(appDir, true); } await main(); Script.complete(); async function main() { try { const browser = await getBrowserPreference(); const html = await download(THREAD_URL); const posts = parsePosts(html); const rootId = (posts.find(p => p.isRoot) || posts[0])?.id; if (!rootId) { throw new Error( "Nenašel jsem zakládající příspěvek." ); } let found = null; for (let i = posts.length - 1; i >= 0; i--) { const p = posts[i]; if ( p.replyTo !== rootId || !p.hasImage ) { continue; } const topic = findTopic(p.text); if (topic) { found = { ...p, topic }; break; } } if (!found) { throw new Error( "Nenašel jsem aktuální zadání." ); } const postUrl = `${THREAD_URL}#commentAnchor${found.id}`; const publishedAt = parseForumDate(found.date); const deadlineAt = publishedAt == null ? null : publishedAt + DEADLINE_DAYS * 24 * 60 * 60 * 1000; const web = new WebView(); /* Tlačítko obsahuje normální HTTPS adresu příspěvku. Když se WebView pokusí adresu načíst, tato funkce požadavek zachytí. WebView navigaci zakáže a příspěvek otevře externě ve zvoleném prohlížeči. */ web.shouldAllowRequest = request => { const url = String(request.url || ""); if (url.startsWith(THREAD_URL)) { openInBrowser( postUrl, browser ); return false; } if ( url === "https://fotohra.local/refresh" ) { Safari.open( URLScheme.forRunningScript() ); return false; } if ( url === "https://fotohra.local/change-browser" ) { try { if (fm.fileExists(prefsPath)) { fm.remove(prefsPath); } } catch (_) {} Safari.open( URLScheme.forRunningScript() ); return false; } return true; }; await web.loadHTML( renderHtml({ threadTitle: extractThreadTitle(html) || "Fotohra", topic: found.topic, author: found.author, exactDate: found.date, freshness: freshnessLabel(found.date), comment: found.text, postUrl, browserName: browser === "opera" ? "Opera" : "Safari", publishedAt, deadlineAt }) ); await web.present(false); } catch (e) { const alert = new Alert(); alert.title = "Fotohra – chyba"; alert.message = String(e); alert.addCancelAction("OK"); await alert.presentAlert(); } } async function download(url) { const req = new Request(url); req.timeoutInterval = 15; req.headers = { "User-Agent": "Mozilla/5.0 iPhone Safari" }; return await req.loadString(); } async function getBrowserPreference() { const prefs = loadPrefs(); if ( prefs.browser === "safari" || prefs.browser === "opera" ) { return prefs.browser; } const alert = new Alert(); alert.title = "Vyber prohlížeč"; alert.message = "Volba se uloží. Později ji změníš dole na kartě."; alert.addAction("Safari"); alert.addAction("Opera"); alert.addCancelAction("Zrušit"); const choice = await alert.presentSheet(); const browser = choice === 1 ? "opera" : "safari"; savePrefs({ browser }); return browser; } function openInBrowser( url, browser ) { if (browser === "opera") { /* Opera pro iOS historicky používá schéma touch-http / touch-https. */ const operaUrl = url.startsWith("https://") ? "touch-https://" + url.slice(8) : url.startsWith("http://") ? "touch-http://" + url.slice(7) : url; Safari.open(operaUrl); return; } Safari.open(url); } function loadPrefs() { try { if (!fm.fileExists(prefsPath)) { return {}; } return JSON.parse( fm.readString(prefsPath) ); } catch (_) { return {}; } } function savePrefs(obj) { fm.writeString( prefsPath, JSON.stringify( obj, null, 2 ) ); } function parsePosts(html) { return html .split( /
]*class="[^"]*go-to-previous/ ) || [] )[1] || null; const textHtml = ( part.match( /
([\s\S]*?)<\/div>/i ) || [] )[1] || ""; return { id, isRoot: /class="[^"]*post-first/ .test( part.slice( 0, 250 ) ), replyTo, hasImage: /class="post-images\b|\/static\/images\/discussionpost\//i .test(part), author: ( part.match( /data-username-canonical="([^"]+)"/ ) || [] )[1] || "neznámý autor", date: htmlToText( ( part.match( /