好歌分享:Rescue
出社會後,平常一天 24 小時,可能有 14 小時都會待在電腦前的我,對於手機功能的需求真的不多,也就不像學生時代會很關注 iOS 上有什麼新的功能。而就在上個月的某天,我ㄧ如往常地在 GitHub 上閒晃時,發現一款叫做 shortcuts-js
的套件,原以為是另一套處理網頁快捷鍵的框架,但看了其敘述寫的是『A JavaScript iOS 12 Shortcuts creator』,我才去查了一下 shortcuts 是什麼...
原來 iOS 12 推出了一個叫 shortcuts 的功能,能讓你透過一連串 Action 的組合,自動化你的 workflow。而該套件的作者很 hardcode 的逆向工程解析 shortcuts 上的程式,撰寫出一套 JavaScript 的工具,讓你能用熟悉的 JavaScript 來撰寫 iOS shortcuts。
覺得蠻有意思的,所以也花了幾個小時的時間玩了一下,透過這篇文章記錄一下過程與心得。
照慣例先給大家看個簡單的成品,我其實做了兩個 shortcuts,一個是利用 Google 的 fact check api 製作的事實查核捷徑,讓你在網頁上快速查驗資訊,但 API 回傳的資料效果不是很好,所以做了另一個 vConsole shortcuts
,讓你能夠在一般網頁(沒有特別在 header 的 CSP 設定 script-src
的網頁)內插入 vConsole
套件的 shortcuts,讓你能在手機上直接打開 console!
想實際玩玩的可以從 iphone 上下載此 iCloud link
下載前記得要到 "設定" -> "捷徑" -> "允許不受信任的捷徑" 將該選項開啟。
下載完後到該 shortcut 的詳細資訊頁內將 在共享工作表中顯示 選項打開,這樣才能在 safari 的網頁分享 panel 找到 shortcut 來執行。
在 Shortcuts 出現之前,有個叫做 Workflow 的應用程式可以讓你透過客製化的腳本,自訂自動化流程,而在 Apple 將其買下後,隨著 iOS 12 一起推出,並且開放免費下載。
Shortcuts 的操作有點像是 Scratch,可以將一個一個的 action block 任意組合,也能夠串接別人做好的 shortcuts,對於不會撰寫程式的人來說,操作上算親民,應該也會覺得蠻有趣的,給大家看一下操作過程(gif 可能有點大):
可以加入各種內建指令,拖拉組合你想要的順序。
action 的 output 可以當作下一個 action 的 input,也可以與 App 互動,例如可以串接 Evernote:
除了上面範例這種簡單的指令堆疊,也支援 if-else
結構:
但是呢,我很難相信會有工程師忍受得了在手機上面一個一個 action 拖拉組合,然後輸入內容,實在太麻煩啦。
還是直接寫程式比較舒服,而對於習慣 JavaScript 的我來說,shortcuts-js
就是一個很棒的選擇!
Shortcuts JS 基本上就是將 Apple 內建的 Action 都製作出對應的 JS 版本,你可以編排與呼叫那些 JS function,shortcuts-js 會幫你轉譯成 iOS 能運行的 shortcut
檔案。
在 Shortcuts JS 的網站上就有一個 playground 讓你實際玩玩看,並且能馬上下載成 shortcut
檔,只是從 iOS 13 開始,不能夠直接將 .shortcut
檔案 AirDrop 到手機內,需要透過 icloud link 才行。
另外,從他們網站上與 github issue 上也能知道目前並不是所有 Apple 內建的 actions 都支援,所以實際上許多 idea 要實作還是不容易。
//////// Import necessary actions and function ////////
const { actionOutput, buildShortcut } = require("@joshfarrant/shortcuts-js");
const {
comment,
wait,
runJavaScriptOnWebPage,
} = require("@joshfarrant/shortcuts-js/actions")
///////////////////////////////////////////////////////
//////// Create and arrange actions ////////
// We'll use this later to reference the output of a calculation
let calcVar = actionOutput();
// Define a list of actions
const actions = [
comment({
text: 'Hello, world!',
}),
number({
number: 42,
}),
calculate({
operand: 3,
operation: '/',
}, calcVar),
showResult({
// Use the Magic Variable
text: withVariables`Total is ${calcVar}!`,
}),
];
/////////////////////////////////////////////
//////// Generate the Shortcut data ////////
const shortcut = buildShortcut(actions);
// Write the Shortcut to a file in the current directory
fs.writeFile('example.shortcut', shortcut, (err) => {
if (err) {
console.error('Something went wrong :(', err);
return;
}
console.log('Shortcut created!');
});
//////////////////////////////////////////////
用 shortcuts js 製作一個基本的 shortcuts 非常簡單,只需要三個步驟:
actions
陣列,將你想要執行的 actions 照順序放進去,基本上每個 action 都會有 output,而每一個 output 都會自動傳給陣列中的下一個 action。可以從官方文件查閱目前 support 的 actions 其輸入輸出為何。不過他們文件沒有很齊全就是了...buildShortcut()
函式來將你編排的 actions 轉化成合法的 iOS shortcuts format,並寫入檔案。接著你就能將這個檔案放入 icloud 上,並產生 icloud shortcuts link 來載入到你的手機裡使用了。
產生 icloud shortcuts link 的方式其實我不是很確定有沒有什麼正確的方式,但我在最後會分享我所使用的方法。
這邊我們先來解釋一下官方範例的 actions 做了什麼事:
const actions = [
comment({
text: 'Hello, world!',
}),
number({
number: 42,
}),
calculate({
operand: 3,
operation: '/',
}, calcVar),
showResult({
// Use the Magic Variable
text: withVariables`Total is ${calcVar}!`,
}),
];
shortcuts js 是用 TypeScript 撰寫的,所以你載入的每個 action 你都能清楚地看到其所需要的參數與類型。
comment
action 如其名,就是在 shortcuts 中下個註解,實際上沒有作用,你也只能在 shortcuts 的 action flow 中看得到它:
number
則是讓你能設定一個數字,他會將你設定的數字當作 output 傳給下一個 action,在範例中即是 calculate
action。
calculate
除了接收上一個 action 傳入的 input 外,你需要指定與 input 操作的 operand
與 operation
;最後的一個參數是一個叫做 Magic Variable 的變數,他可以用來儲存 action 所產生的結果,並在其他 action 中 reference 使用,例如範例中的 showResult
。
showResult
就是將你傳入的 text
參數值輸出到手機畫面中。在這邊我們想要取得 caculate
所 output 的變數值,然而 showResult
需要的是字串,我們不能單純的傳入變數,必須要使用一個特殊的函數 withVariables
,將變數值讀出並轉成 string 格式。
這個 shortcuts 執行後就是會在你的手機上跳出一個 dialog 並顯示 14 這個數字。
如同前言提到的,我一開始想做的是事實查核的 shortcuts,也就是說讓使用者點選 shortcuts 後,可以輸入想查詢的事情,背後呼叫 Google 的 fact check api,但是 shortcuts 雖然有提供 Get URL content
的 aciton,卻沒有方便的資料處理 action,必須使用 dictionary
之類的 action 來將回傳的 JSON format 資料轉存成辭典格式,操作上很繁瑣,可以參考 reddit 上高手的說明:retrieving data、parsing complex api responses、scrapign web data。
在花了不少時間嘗試用 shortcuts js 按照 reddits 上的做法實作後,還是不成功,正當想放棄時,我看到了一個 action 叫做 runJavaScriptOnWebPage()
,可以讓你在網頁上插入 JavaScript 運行。
看到這個心花怒放一下,覺得撿到寶,可以在網頁上運行的話更好,在想進行事實查核的網頁上啟用這個 shortcuts,然後一切流程都直接用 JavaScript 在 web page 上執行就好,就不需要透過 shortcuts action 來處理資料了!
利用 runJavaScriptOnWebPage()
所兜出來的 shortcuts 主要程式如下:
const actions = [
runJavaScriptOnWebPage({
text: `
const element = document.querySelector('title');
const title = element.textContent;
const url = 'https://content-factchecktools.googleapis.com/v1alpha1/claims:search?key=GoogleAPIKey&query=' + title;
const result;
fetch(url)
.then(function(result) {
return result.json();
})
.then(function(data) {
result = data;
completion(result);
});
`,
}, output),
quickLook(),
];
不是很確定其 JavaScript 執行的環境為何,我並沒有去研究,但測試後是無法使用 await 的,使用單純的 promise 則可以。
運行結果如下:
看起來只要把 response 在美化一下就好,但是我發現 Google API 回傳的結果沒有很好,會回傳很多不相干的,就也不想繼續優化了。
不過既然知道可以在網頁上運行 JavaScript,那就可以做更多事情了!
像是我後來找到的這位高手,就利用這樣的方式來 bypass Youtube video 在手機網頁上無法背景播放的問題,是我目前覺得最實用的 shortcuts XD
而前言中的範例,我也是利用相同方式,將 vConsole
inject 到頁面中,程式碼非常簡單:
// injectScript.js
module.exports = () => {
const script = document.createElement('script');
script.src = "https://cdn.bootcss.com/vConsole/3.2.0/vconsole.min.js";
document.getElementsByTagName('head')[0].appendChild(script);
setTimeout(() => {
// hacky way to wait for script load
const script2 = document.createElement('script');
script2.text = 'window.vConsole = new VConsole();';
document.body.appendChild(script2);
}, 1000);
completion();
};
本來是想直接利用 script.onload
在 vConsole
的 script 載入後進行 initial,但是不知道為何無法順利觸發,因此只好用 setTimeot 的方式快速 hack 一下。(這種實作會造成有時得啟用兩次 shortcut 才能成功載入 vConsole ...)
const injectScript = require("./injectScript");
// ...略
runJavaScriptOnWebPage(
{
text: `
const injectScript = ${injectScript.toString()};
const result = injectScript();
completion(result);`
},
output
),
// ...略
完整程式碼我放在 GitHub。
前面說過從 iOS 13 開始,不能夠再隨意用 AirDrop 丟入 .shortcut
的檔案,必須要透過 iCloud link。
但我其實找不太到該如何將我產生的 .shortcut
檔案轉成 iCloud link,後來在 GitHub issue 上找到有人分享他從 reddit 找到的 shortcuts - Import Shortcut。
只要將你做好的 .shortcut
檔案放入 iCloud Drive,透過這個 Import Shortcut 就能從你的 iCloud Drive 中將檔案抓下來並產生一個 iCloud link,接著理論上 iPhone 就會自動 detect 到,你也就能直接安裝。
P.S. 再提醒一下,記得要到 "設定" -> "捷徑" -> "允許不受信任的捷徑" 將該選項開啟,才能 import 你自己製作的 shortcuts。
shortcuts 這個概念蠻有意思的,一些小動作其實自己一步一步做也是可以,但透過 shortcuts 將常用的動作組織起來,一鍵就能完成,每個步驟省個三、四秒,整體可能就能省掉你十秒,如果這 shortcuts 很常用的話,勢必能增加效率,至少心理層面的感覺有 XD
整體來說,shortcuts js 還有很多地方可以改進,目前我覺得還缺少一些蠻重要的 Action,一些輸入的參數目前也不是太好用,debug 也不方便(必須要裝載上去才能測試),希望透過這次介紹能引起多點人的興趣,或許能更加推進這個專案。