Repository Public Research preview
1# AppleScript recipes23Each file in `scripts/recipes/` is a complete program for `osascript`, tested against Safari on a local4site. Values are passed as arguments, never pasted into source. Typical sequence:56```bash7W=$(osascript scripts/recipes/open-window.applescript "https://example.org/")8osascript scripts/recipes/wait-for-load.applescript "$W" 1 309osascript scripts/recipes/get-text.applescript "$W" 110osascript scripts/recipes/fill-field.applescript "$W" 1 'input[name="q"]' "tide tables"11osascript scripts/recipes/click.applescript "$W" 1 'button[type="submit"]'12osascript scripts/recipes/wait-for-load.applescript "$W" 1 3013osascript scripts/recipes/fetch-base64.applescript "$W" 1 /export.csv | base64 -D > export.csv14screencapture -x -o -l "$W" page.png # the window id is the system window number15osascript scripts/recipes/close-window.applescript "$W"16```1718Keep the window id from the first step and use it everywhere. Wait after every step that19navigates. `fill-field` and `click` print `not-found` when the selector matches nothing: check it.2021## `open-window.applescript`2223```applescript24-- Open a URL in a NEW Safari window and print that window's id.25-- Usage: osascript open-window.applescript "https://example.com/"26-- Keep the id: it is stable for the life of the window, unlike "window 1" or "front window",27-- which change whenever the user clicks another window.28on run argv29 tell application "Safari"30 make new document with properties {URL:(item 1 of argv)}31 return id of window 132 end tell33end run34```3536## `wait-for-load.applescript`3738```applescript39-- Wait until a tab has finished navigating. Prints "loaded" or "timeout".40-- Usage: osascript wait-for-load.applescript <window-id> <tab-index> [timeout-seconds]41-- Safari sets "URL of tab" to the destination as soon as a navigation starts, while the old42-- document is still the one that answers JavaScript. The page is ready when the document's own43-- URL has caught up AND readyState is "complete" — checking readyState alone reports the OLD page.44on run argv45 set w to (item 1 of argv) as integer46 set t to (item 2 of argv) as integer47 set limit to 3048 if (count of argv) > 2 then set limit to (item 3 of argv) as integer49 set deadline to (current date) + limit50 set calm to 051 repeat while (current date) < deadline52 try53 tell application "Safari"54 set tabURL to URL of tab t of window id w55 set state to do JavaScript "location.href + '\\n' + document.readyState" in tab t of window id w56 end tell57 if state is (tabURL & linefeed & "complete") then58 set calm to calm + 159 if calm ≥ 2 then return "loaded"60 else61 set calm to 062 end if63 on error64 set calm to 0 -- the tab is between documents: try again65 end try66 delay 0.267 end repeat68 return "timeout"69end run70```7172## `list-tabs.applescript`7374```applescript75-- List the tabs of one window: index, URL and title, tab-separated, one per line.76-- Usage: osascript list-tabs.applescript <window-id>77on run argv78 set w to (item 1 of argv) as integer79 set out to ""80 -- Inside a Safari "tell" block the word "tab" means a browser tab, not the tab character:81 -- name the separator outside it.82 set sep to character id 983 tell application "Safari"84 repeat with t in tabs of window id w85 set u to URL of t86 if u is missing value then set u to ""87 set n to name of t88 if n is missing value then set n to ""89 set out to out & (index of t) & sep & u & sep & n & (character id 10)90 end repeat91 end tell92 return out93end run94```9596## `run-js.applescript`9798```applescript99-- Run JavaScript in a tab and print the result.100-- Usage: osascript run-js.applescript <window-id> <tab-index> '<javascript>'101-- The code travels as an argument, so it needs no AppleScript escaping.102-- Return a string (JSON.stringify(...)): objects, undefined and promises come back empty.103on run argv104 set w to (item 1 of argv) as integer105 set t to (item 2 of argv) as integer106 tell application "Safari"107 set r to do JavaScript (item 3 of argv) in tab t of window id w108 end tell109 try110 return r111 on error112 return ""113 end try114end run115```116117## `get-text.applescript`118119```applescript120-- Print the visible text of a tab.121-- Usage: osascript get-text.applescript <window-id> <tab-index>122on run argv123 set w to (item 1 of argv) as integer124 set t to (item 2 of argv) as integer125 tell application "Safari"126 return do JavaScript "document.body ? document.body.innerText : ''" in tab t of window id w127 end tell128end run129```130131## `fill-field.applescript`132133```applescript134-- Set the value of a form field so that the page's own code notices.135-- Usage: osascript fill-field.applescript <window-id> <tab-index> '<css-selector>' '<value>'136-- Assigning el.value alone is ignored by React, Vue and similar frameworks: they patch the137-- property. Calling the native setter and then firing "input" and "change" works everywhere.138-- Prints "ok", or "not-found".139on run argv140 set w to (item 1 of argv) as integer141 set t to (item 2 of argv) as integer142 set js to "(function(sel, val){var el=document.querySelector(sel); if(!el) return 'not-found';" & ¬143 "var proto = el.tagName==='TEXTAREA' ? HTMLTextAreaElement.prototype : el.tagName==='SELECT' ? HTMLSelectElement.prototype : HTMLInputElement.prototype;" & ¬144 "Object.getOwnPropertyDescriptor(proto,'value').set.call(el, val);" & ¬145 "el.dispatchEvent(new Event('input',{bubbles:true})); el.dispatchEvent(new Event('change',{bubbles:true})); return 'ok';})"146 -- JSON.stringify-style quoting of the two arguments is done in JavaScript, not here.147 set call to js & "(decodeURIComponent('" & my encode(item 3 of argv) & "'), decodeURIComponent('" & my encode(item 4 of argv) & "'))"148 tell application "Safari"149 return do JavaScript call in tab t of window id w150 end tell151end run152153-- Percent-encode every byte, so no character of the value can break out of the JavaScript string.154on encode(s)155 return do shell script "/usr/bin/python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=\"\"))' " & quoted form of s156end encode157```158159## `click.applescript`160161```applescript162-- Click the first element matching a CSS selector. Prints "ok" or "not-found".163-- Usage: osascript click.applescript <window-id> <tab-index> '<css-selector>'164-- el.click() runs the page's handlers and follows links. It does not move the real pointer.165on run argv166 set w to (item 1 of argv) as integer167 set t to (item 2 of argv) as integer168 set sel to do shell script "/usr/bin/python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=\"\"))' " & quoted form of (item 3 of argv)169 tell application "Safari"170 return do JavaScript "(function(s){var el=document.querySelector(s); if(!el) return 'not-found'; el.scrollIntoView({block:'center'}); el.click(); return 'ok';})(decodeURIComponent('" & sel & "'))" in tab t of window id w171 end tell172end run173```174175## `fetch-base64.applescript`176177```applescript178-- Download a URL from inside the page — with the site's cookies and sign-in — and print it as179-- base64. Pipe the output into a file:180-- osascript fetch-base64.applescript <window-id> <tab-index> '/path/or/url' | base64 -D > file.bin181-- Prints "HTTP <status>" on stderr-like first line when the server refuses. Same-origin URLs only182-- (the browser's own rules). Suited to files up to a few megabytes; larger ones need chunking.183on run argv184 set w to (item 1 of argv) as integer185 set t to (item 2 of argv) as integer186 set u to do shell script "/usr/bin/python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=\"\"))' " & quoted form of (item 3 of argv)187 -- do JavaScript cannot wait for a promise, so the request is synchronous.188 set js to "(function(u){var x=new XMLHttpRequest(); x.open('GET',u,false); x.overrideMimeType('text/plain; charset=x-user-defined'); x.send();" & ¬189 "if(x.status<200||x.status>=300) return 'HTTP '+x.status; var s=x.responseText, b=''; for(var i=0;i<s.length;i++) b+=String.fromCharCode(s.charCodeAt(i)&255); return btoa(b);})(decodeURIComponent('" & u & "'))"190 tell application "Safari"191 return do JavaScript js in tab t of window id w192 end tell193end run194```195196## `close-window.applescript`197198```applescript199-- Close a window by id. Usage: osascript close-window.applescript <window-id>200on run argv201 tell application "Safari" to close window id ((item 1 of argv) as integer)202end run203```204