Well in Time
用 JS 玩轉 iOS shortcuts
04-25-202011 Min Read

好歌分享: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 來執行。

project-demo

iOS Shortcuts

在 Shortcuts 出現之前,有個叫做 Workflow 的應用程式可以讓你透過客製化的腳本,自訂自動化流程,而在 Apple 將其買下後,隨著 iOS 12 一起推出,並且開放免費下載。

Shortcuts 的操作有點像是 Scratch,可以將一個一個的 action block 任意組合,也能夠串接別人做好的 shortcuts,對於不會撰寫程式的人來說,操作上算親民,應該也會覺得蠻有趣的,給大家看一下操作過程(gif 可能有點大):

shortcuts-ops-demo

可以加入各種內建指令,拖拉組合你想要的順序。

action 的 output 可以當作下一個 action 的 input,也可以與 App 互動,例如可以串接 Evernote:

interact-with-app

除了上面範例這種簡單的指令堆疊,也支援 if-else 結構:

if-else

但是呢,我很難相信會有工程師忍受得了在手機上面一個一個 action 拖拉組合,然後輸入內容,實在太麻煩啦。

還是直接寫程式比較舒服,而對於習慣 JavaScript 的我來說,shortcuts-js 就是一個很棒的選擇!

Shortcuts JS

shortcuts-js-official

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 非常簡單,只需要三個步驟:

  1. 載入你想要使用的 actions。
  2. 創建一個 actions 陣列,將你想要執行的 actions 照順序放進去,基本上每個 action 都會有 output,而每一個 output 都會自動傳給陣列中的下一個 action。可以從官方文件查閱目前 support 的 actions 其輸入輸出為何。不過他們文件沒有很齊全就是了...

shortcut-js-docs

  1. 最後一個步驟就是透過 shortcuts js 的 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 中看得到它:

comment-sample

number 則是讓你能設定一個數字,他會將你設定的數字當作 output 傳給下一個 action,在範例中即是 calculate action。

calculate 除了接收上一個 action 傳入的 input 外,你需要指定與 input 操作的 operandoperation;最後的一個參數是一個叫做 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 dataparsing complex api responsesscrapign 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 則可以。

運行結果如下:

fact-check-demo

看起來只要把 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.onloadvConsole 的 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 也不方便(必須要裝載上去才能測試),希望透過這次介紹能引起多點人的興趣,或許能更加推進這個專案。

資料來源

  1. shortcuts-js website
  2. shortcuts-js github
  3. fact check api
  4. retrieving data
  5. parsing complex api responses
  6. scrapign web data
© by Arvin Huang. All rights reserved.
theme inspired by @mhadaily
Last build: 09-08-2024