AppleScript 初体験

✍🏼 作成日 2022年05月03日    💡 更新日 2023年01月05日
❗️ 注意:この記事が作成されてから既に 日が経過しています。情報の鮮度にご注意ください
🖥  説明:AppleScriptを少し体験してみて、よく使われるユーザーインタラクションの方法を紹介しましたが、非常に強力だと感じました。
📚  Craft にも公開: https://www.craft.do/s/RsgimVt9VBxAmg

はじめに

最近新しいMac Studioを購入しました。M1 Maxチップと64GBのメモリを搭載しており、非常に高性能です。一方で、以前のブログ処理フロー]では、Craftから画像を取得する際(このステップは速いです、両者とも海外にあるため)、Tencent Cloud COSにアップロードする際にネットワークが遅い問題によく遭遇していました。

SCR-20220502-q47

調査したところ、GithubはActionサーバーのリージョンを設定する機能をサポートしていない]ようです。このため、今後は公開プロセスをローカルで行い、ビルドプロセスを高速化することにしました。これがこの記事を書いたきっかけです。以下のAppleScriptはASと略称します。

なぜAppleScriptを選んだのか?

まず、操作の手順と複雑さを可能な限り短縮したいと考え、以下の操作手順を定めました:

  1. Craftの公開ボタンをクリックする。
  2. ローカルでビルドし、ターミナルにビルドログを表示する。
  3. ビルド結果をgithubにプッシュする。

あるアプリから別のアプリを呼び出す必要があるため、AppleScriptが唯一の選択肢でした。ただし、ASを使用していますが、実際に実行されるのは既存のjsファイルであり、craft_publish_ciのworkflowタスク]をローカルで実行するようにしただけです。

AppleScriptの概要

概念図:

execute_script_2x

AppleScriptでできること

  1. ユーザーとの対話が可能。例えばユーザー入力への応答、通知センターでの通知送信など。
  2. 他のアプリを制御可能。他のアプリがASインターフェースを提供している場合は直接制御でき、提供していない場合はAS構文を使用してインターフェース上のボタンなどを操作する必要があります。
  3. 自動化タスクの実行が可能。メール送信、タイマースクリプトの実行、音楽プレーヤーの起動、ロック画面、カスタムURL Schemeへの応答後のスクリプト実行など。

基本構文

  • 行頭に---(3つのハイフン)または#を付けると、その行がコメントになります。
  • (*``*xxx**``)(括弧+アスタリスク)で囲まれたxxxはブロックコメントとしてxxxの内容をコメントアウトします。
  • その他の基本構文は口語に似ていますが、英語の文法に自信がない場合(besides、against、overなどの具体的な意味が分からない場合)は、基本的な構文を使用することをお勧めします。

AppleScriptの形式

AppleScriptには一般的に2つの形式があります:Script(スクリプト)とApplication(アプリケーション)。ここではこの2つを紹介します。Script bundleとTextはここでは取り上げません。

SCR-20220502-qqc

注:同じコードでも、スクリプトとして保存して実行する場合とアプリケーションとして保存して実行する場合では、インターフェースが異なります。

スクリプト

スクリプトは、jsのような実行可能ファイルですが、asはMacのデスクトップ環境で実行され、jsはChromeで実行されます。ダブルクリックで実行できます。以下はスクリプトprocessが進捗を表示するインターフェースで、スクリプトの右上にある「実行」ボタンをクリックして実行します:

SCR-20220502-r3e

アプリケーション

Applicationとして保存されたASの拡張子は、Safari、Chrome、WeChatなどの通常のアプリケーションと同じで、.appで終わり、パッケージ内容を確認することもできます。URL Schemeでの呼び出し、ダブルクリック、アプリアイコンへのドラッグなど、さまざまな実行方法があります。関連するイベント関数を記述して、ユーザー操作に対応する必要があります。以下はアプリケーションprocessが進捗を表示するインターフェースで、「xxx.app」をダブルクリックして実行します:

SCR-20220502-r33

その他

上記の2つ以外はあまり使用されませんが、スクリプトをサービスとして保存し、ステータスバーや任意のインターフェースの右クリックメニューのサービスからスクリプトを呼び出す必要がある場合は、上記以外の形式が必要です:

SCR-20220502-rcr

スクリプトをシステムServiceとして保存することを選択できます。この場合、上記のprocessコードが実行されると、進捗バーが上部のステータスバーに表示されます:

scriptmenu_progress_2x

よく使われる操作コード

以下の操作は「アプリケーション」タイプのASに基づいています。以下の構文はターミナルでosascript -e '语法内容'を使用して簡単に検証できます。ただし、process進捗バーの表示のような複数行の表現は、osascriptでは実現できません。tellのような複数行コマンドでは複数の-eパラメータをosascript -e ‘tell application “Finder”’ -e ‘end tell’のように連結して使用できますが、この構文はprocess進捗バーには無効です。

ユーザーインタラクション

通知センターに情報を表示

1
display notification "通知内容" with title "通知 title" subtitle "通知副标题"

SCR-20220502-sfl

ポップアップ

1
display dialog "这是个通知"

Image

ユーザー入力の取得

1
display dialog "What's your name?" default answer "" with icon note buttons {"取消", "确认"} default button "确认"

Image

注:ASではフォームのようなコンポーネントを生成することができず、上記のようなダイアログ(ウェブのpromptに類似)しか生成できません

指定テキストの再生

1
say "What is your name?" using "Alex" speaking rate 140 pitch 42 modulation 60

その他

ユーザーにフォルダ、ファイル、色の選択やリストからの項目選択などをさせる機能は、ここでは省略します。

コマンドラインの実行

1
do shell script "echo $PATH"

注意:コマンドラインのPATH環境変数は/usr/bin:/bin:/usr/sbin:/sbinであるため、nodenvmなど後からインストールしたコマンドを直接実行できません。これらのコマンドを実行する際は、手動でコマンドの場所を指定する必要があります。

Nodeスクリプトの実行:

1
2
3
set node to "/Users/x/.nvm/versions/node/v14.19.1/bin/node"
set appPath to "/Applications/Xhelper.app/Contents/Resources/Scripts/"
do shell script node & " " & appPath & "index.js"

前のステートメントの出力結果を取得

直前のステートメントの直後にresultと記述することで結果を取得できます:

1
2
do shell script "echo $PATH"
display dialog result with title "通知"

Image

デバッグ情報の表示

logステートメントを使用し、Script Editorの下にあるRepliesで出力情報を確認できます。ここではshell scriptコマンド実行中にスクリプトがコンソールに出力した内容(JSスクリプト実行時のconsole.logなど)を確認できます。

1
log do shell script "echo $PATH"

SCR-20220502-sov

進捗状況の表示

進捗表示にはprogressを使用しますが、状態に応じた表示が必要なため構文が複雑になります:

1
2
3
4
5
6
7
8
9
set progress total steps to 3
set progress completed steps to 0
set progress description to "处理中..."
delay 1
set progress completed steps to 1
delay 1
set progress completed steps to 2
delay 1
set progress completed steps to 3

関連スクリーンショットは上記で確認できます。

一定時間遅延後の実行

1
delay n

nは秒数(小数可)を表します。ユーザー操作のシミュレーション時に、ページの表示遅延などに対処するために使用します。奇妙な問題も遅延実行で解決できる場合があり、このステートメントはjssetTimeout手法に類似しています。

他のアプリケーションの呼び出し

注意:システム警告が表示され、実行には確認が必要です:

Image

確認後、设置-安全与隐私-隐私-自动化で確認できます:

Image

他のアプリケーションを起動して操作を実行できます。以下はTerminalアプリを起動後、アクティブにしてコマンドを実行する例です:

1
2
3
4
5
tell application "Terminal"
if not (exists window 1) then reopen
activate
do script "echo $PATH" in window 1
end tell

このステートメントでは、最初に開いたウィンドウが存在すればそれをアクティブにし(do script)、存在しなければ新規ウィンドウでコマンドを実行します。各do scriptは新しいTerminalを開くため、この判定によりウィンドウの再利用が可能です。ただし、window 1内に既存プロセス(例:中断されていないserver)がある場合、コマンドは実行されないため追加の判定が必要ですが、ここでは詳述しません。

Image

URL Schemeへの応答

ASスクリプトを探してダブルクリックで起動したくない場合や、他のアプリからASアプリを呼び出したい場合にURL Schemeを使用します。少数派に](https://sspai.com/post/31500)の解説記事があります。

まずInfo.plistファイルを修正する必要があります。Script Editorの右側で「Finderで表示」を右クリック:

Image

一つ上の階層に移動すると該当ファイルがあります:

Image

内容はXML形式で、以下のフィールドを追加します:

1
2
3
4
5
6
7
8
9
10
<array>
<dict>
<key>CFBundleURLName</key>
<string>Open File</string>
<key>CFBundleURLSchemes</key>
<array>
<string>xhelper</string>
</array>
</dict>
</array>

XCodeでファイルを開き、以下のようなエントリを確認:

Image

追加成功を意味します。

この操作は、ASアプリがxhelper(既存と重複しない任意の文字列に変更可能)で始まるURLに応答するよう設定します。

設定後、Script Editorで保存するか、cmd + Lするか、一度実行する(または全て行う)ことで再コンパイルされ有効になります。

URLに応答させるため、イベントリスナーを設定:

1
2
3
on open location this_URL
display dialog this_URL
end open location

上記コードで、this_URLxhelperで開いたリンクです。

ブラウザ(スクリーンショットはChrome)でリンクをテスト:

Image

ASアプリが応答し、xhelper://你好!がポップアップ表示されます(encodeエンコード不要)。

私の使用例

ここで私のCraft buildコードを公開します。前述のURL Schemeを使用しており、nodeで実行するJSは](/tech/my-blog-ci-in-2022.html)の以前のワークフローから改造したものです。新しいロジックはなく、ASの構文は口語的で分かりやすいため解説は省略します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
on open location this_URL
try
set startOffset to offset of "://" in this_URL
set the content to text from (startOffset + 3) to -1 of this_URL
set node to "/Users/x/.nvm/versions/node/v14.19.1/bin/node"
set appPath to "/Applications/Xhelper.app/Contents/Resources/Scripts/"
--- 如果有两个参数表示需要发布到 wechat,如果只有一个参数则参数即为 base64 编码的内容
set AppleScript's text item delimiters to "&"
set arguments to every text item of the content
set AppleScript's text item delimiters to ""
set listLength to the length of arguments
if listLength = 2 then
set realContent to item 2 of arguments
do shell script "echo " & realContent & " > " & appPath & "content.base64.txt"
tell application "Terminal"
if not (exists window 1) then reopen
activate
set alive to do script node & " " & appPath & "index.js" in window 1
--- 等待上一个脚本执行完毕后再执行下一个脚本
repeat
delay 0.1
if not busy of alive then exit repeat
end repeat
beep
do script node & " " & appPath & "wechat.js" in window 1
end tell
else
do shell script "echo " & content & " > " & appPath & "content.base64.txt"
tell application "Terminal"
if not (exists window 1) then reopen
activate
do script node & " " & appPath & "index.js" in window 1
end tell
end if
on error error_message
display dialog error_message buttons {"Cancel"} default button 1
end try

end open location

効果:

Image

参考リンク

- EOF -
この記事の初出: AppleScript 初体験 - Xheldon Blog