そうだiOSアプリを常に最新の状態にしておこう!

    • 年齢認証ダイアログを突破できない問題を修正しました。
    • 予約時間を変更できない問題を修正しました。
    • 除外アプリを指定できるようにしました。>>移動する

最新バージョンが必ずしも良いものだとは限らないのだけど、アプリのアップデートをお知らせする件数バッジが増えてくると、それなりに気になる。最近はWiFi経由でもiTunesとの同期が可能になった。いちいちUSBケーブルで接続していた頃と比べれば、かなりお手軽に同期できる環境になったとも言える。しかし、人間一度「楽」を覚えると、さらなる「楽」を求めてしまうものである。(特に自分の場合は)

  • iTunesを起動して、
  • Appを選択して、
  • OO個のアップデートが利用可能をクリックして、
  • iTunes Storeの無料アップデートをダウンロードをクリックする。
  • すべてのダウンロードが完了したら、同期する。

たったこれだけの操作なのだけど、この一連の操作が面倒になってくるのだ。特にダウンロードの完了を待ってから、同期するまでの時間差が煩わしい。アップデートが溜まっている&回線が遅いのでダウンロードに時間がかかる...。ダウンロード途中でスリープさせてしまったり、ダウンロードは完了したけど同期を忘れてしまったり、同期途中でWiFiの圏外へ持ち出してしまったりと、中途半端なことばかりになる。そして、保持しているアプリが増えるほどに、アップデートお知らせ攻撃は頻繁になってくる。もうイヤ...。

というわけで、すべての操作を自動化して、アプリを最新の状態にアップデートしくれる方法はないのかと、調べ始めてしまうのであった。いつものAppleScriptの出番である。

作業環境

iTunesを操作する

  • AppleScriptにはGUIを操作する機能が備わっているので、理論的には人が行うあらゆる操作はコーディング可能である。
  • ところが、コーディングは可能なのだが、目的のGUIを表現するコードを見つけるのにひと苦労してしまう...。
  • とにかく、一連の操作ができることを第一目標に、コードの読みにくさには目をつぶって、何とかできたのが以下。


tell application "iTunes" to activate
tell application "System Events"
tell process "iTunes"
repeat until exists window "iTunes"'s scroll area --iTunes起動待ち
do shell script "/usr/local/bin/growlnotify 'iTunes起動待ち'"
delay 1
end repeat
set side_bar to (window 1's scroll area 1 whose description = "ソース")'s item 1's outline 1
select (side_bar's row 1 whose static text 1's name starts with "App") --ライブラリの「App」選択
keystroke "r" using {command down} --アップデートを確認
try
repeat until (side_bar's row 1 whose selected = true)'s static text 1's value = "iTunes Store" --Store遷移待ち
if exists (windows whose subrole = "AXDialog") then --アップデートなしダイアログ
click window 1's button "OK"
error number -128
end if
end repeat
click (window "iTunes"'s UI element "iTunes Store"'s UI element "無料アップデートをダウンロード") --無料アップデートをダウンロード
repeat while exists (windows whose subrole = "AXDialog") --Store認証ダイアログ
delay 1
end repeat
set loop to 0
repeat until exists (side_bar's rows whose static text 1's value starts with "ダウンロード") --アップデート待ち
if exists (windows whose subrole = "AXDialog") then --年齢認証ダイアログ
click window 1's button "OK"
end if
delay 1
set loop to loop + 1
do shell script "/usr/local/bin/growlnotify 'アップデート待ち' -m " & loop & "回目"
if loop > 10 then error number -128
end repeat
select (side_bar's row 1 whose static text 1's value starts with "ダウンロード") --ライブラリの「ダウンロード」選択
do shell script "/usr/local/bin/growlnotify " & quoted form of (result's static text 1's value as text) repeat while exists (side_bar's rows whose static text 1's value ends with "ダウンロード中") --ダウンロード待ち
delay 2
end repeat
do shell script "/usr/local/bin/growlnotify 'ダウンロード完了'"
select (side_bar's row 1 whose static text 1's name starts with "App") --ライブラリの「App」選択
my sync() --同期
repeat 10 times
delay 2
if exists (windows whose subrole = "AXDialog") then --転送ダイアログ
delay 1
keystroke return --デフォルトボタンを押す
end if
end repeat
repeat while exists (window 1's scroll area 1's static text 1) --同期待ち
delay 1
end repeat
do shell script "/usr/local/bin/growlnotify '同期完了'"
on error msg number num
if num-128 then do shell script "/usr/local/bin/growlnotify -s -m " & quoted form of msg & num
end try
end tell
end tell
tell application "iTunes" to quit

--iOSバイス同期
on sync() do shell script "/usr/local/bin/growlnotify 'sync'"
tell application "iTunes"
set devices to sources whose kind = iPod
repeat with aDevice in devices
update aDevice
end repeat
end tell
end sync

コードを整える

  • 一連の操作が可能になったらしめたもの。
  • 操作ごとにメソッドに切り出し、メンテナンスし易いコードに書き換える。
  • そして、そこからうまく動かない状況を排除する仕組みを追加していく。
  • そのようにして、上記コードは以下のように変化した。


set is_running to iTunes_exists()
tell application "iTunes" to activate
tell application "iTunes" to reopen
tell application "System Events"
tell process "iTunes"
my iTunes_activate_waiting() --起動待ち
select my iTunes_side_bar("App") --ライブラリの「App」選択
keystroke "r" using {command down} --アップデートを確認
try
my iTunes_app_update_waiting() --アップデート確認待ち
click (window "iTunes"'s UI element "iTunes Store"'s UI element "無料アップデートをダウンロード") my iTunes_store_auth_waiting() --ストア認証ダイアログの認証待ち
my iTunes_download_is_beginnig_waiting() --ダウンロード開始待ち
my iTunes_age_auth_OK() --年齢認証ダイアログでOK
my iTunes_download_waiting() --ダウンロード待ち
my iTunes_sync() --同期
my iTunes_sync_waiting() --同期処理待ち
on error msg number num
if num-128 then my growlnotify("-s -m " & quoted form of msg & num) end try
end tell
end tell

if is_running then
tell application "System Events" to keystroke tab using {command down} else
tell application "iTunes" to quit
end if




--iTunesが起動しているか返す
on iTunes_exists() tell application "System Events"
tell process "iTunes"
exists
end tell
end tell
end iTunes_exists

--iTunes起動待ち
on iTunes_activate_waiting() tell application "System Events"
tell process "iTunes"
repeat until exists window "iTunes"'s scroll area --iTunes起動待ち
my growlnotify("iTunes起動待ち") delay 1
end repeat
end tell
end tell
end iTunes_activate_waiting

--サイドバーを返す
on iTunes_side_bar(item_name) tell application "System Events"
tell process "iTunes"
try
set frontmost to true
set side_bar to (window 1's scroll area 1 whose description = "ソース")'s item 1's outline 1
on error msg number num
if num = -1719 then
set frontmost to true
pick (menu item "iTunes Store ではフルウインドウを使用" of menu "表示" of menu bar item "表示" of menu bar 1) delay 2
set side_bar to (window 1's scroll area 1 whose description = "ソース")'s item 1's outline 1
else
error msg number num
end if
end try
if item_name = "" then
side_bar
else
side_bar's row 1 whose static text 1's value starts with item_name
end if
end tell
end tell
end iTunes_side_bar

--ダイアログが表示されているか?
on iTunes_dialog_exists() tell application "System Events"
tell process "iTunes"
set frontmost to true
exists (windows whose subrole = "AXDialog") end tell
end tell
end iTunes_dialog_exists

--アップデート確認待ち(AppからiTunes Storeへの遷移待ち)
on iTunes_app_update_waiting() tell application "System Events"
tell process "iTunes"
set frontmost to true
repeat until (my iTunes_side_bar("")'s row 1 whose selected = true)'s static text 1's value = "iTunes Store" --iTunes Store遷移待ち
if my iTunes_dialog_exists() then --アップデートなしダイアログ
click window 1's button "OK"
error number -128
end if
end repeat
end tell
end tell
end iTunes_app_update_waiting

--Store認証ダイアログの認証待ち
on iTunes_store_auth_waiting() tell application "System Events"
tell process "iTunes"
repeat while my iTunes_dialog_exists() --Store認証ダイアログ
delay 1
end repeat
end tell
end tell
end iTunes_store_auth_waiting

--無料アップデートをダウンロード→ダウンロード開始まで遷移待ち
on iTunes_download_is_beginnig_waiting() tell application "System Events"
tell process "iTunes"
set frontmost to true
set loop to 0
repeat until exists (my iTunes_side_bar("")'s rows whose static text 1's value starts with "ダウンロード") my iTunes_age_auth_OK() set loop to loop + 1
my growlnotify("'アップデート待ち' -m " & loop & "回目") if loop > 10 then error number -128
end repeat
end tell
end tell
end iTunes_download_is_beginnig_waiting

--年齢認証ダイアログでOK
on iTunes_age_auth_OK() tell application "System Events"
tell process "iTunes"
set frontmost to true
delay 1
repeat while my iTunes_dialog_exists() click window 1's button "OK"
my growlnotify("'年齢認証OK'") end repeat
end tell
end tell
end iTunes_age_auth_OK

--ダウンロード待ち
on iTunes_download_waiting() tell application "System Events"
tell process "iTunes"
select my iTunes_side_bar("ダウンロード") --ライブラリの「ダウンロード」選択
--do shell script "/usr/local/bin/growlnotify " & quoted form of (result's static text 1's value as text)
my growlnotify(quoted form of (result's static text 1's value as text)) repeat while exists (my iTunes_side_bar("")'s rows whose static text 1's value ends with "ダウンロード中") --ダウンロード待ち
delay 2
end repeat
my growlnotify("ダウンロード完了") select my iTunes_side_bar("App") --ライブラリの「App」選択
end tell
end tell
end iTunes_download_waiting

--iOSバイスを同期する
on iTunes_sync() my growlnotify("同期") tell application "iTunes"
set devices to sources whose kind = iPod
repeat with aDevice in devices
update aDevice
end repeat
end tell
end iTunes_sync

--同期処理待ち
on iTunes_sync_waiting() tell application "System Events"
tell process "iTunes"
repeat 5 times --後半で一瞬処理が途切れるため、念のため5回繰り返す
repeat while exists (window 1's scroll area 1's static text 1) --同期待ち
delay 1
if my iTunes_dialog_exists() then --転送ダイアログ
delay 1
keystroke return --デフォルトボタンを押す
end if
end repeat
end repeat
end tell
end tell
my growlnotify("同期完了") end iTunes_sync_waiting

--growl表示
on growlnotify(args) try
do shell script "/usr/local/bin/growlnotify " & args
end try
end growlnotify

予約設定とスリープ復帰

  • 以上のスクリプトを毎日夜中1時に実行するのだ。
  • 夜中寝静まる頃、MacBookはスリープしている。
  • だから、MacBookを起こしてあげる必要もある。
  • 毎日夜中1時に実行するのはlaunchdに設定しておく。
  • スリープ復帰は、省エネルギー設定のスケジュール...を利用する。


property update_button : "iTunes Appアップデート"
property schedule_button : "予約する/予約解除"

on run
GUI_scripting_check() launchd_init() activate
"予約時間を入力してください。\n(例:1=1時、23=23時、入力なし=予約解除)"
set res to display dialog result default answer plist_hour() buttons {"キャンセル", schedule_button, update_button} default button 3 with icon 1
if res's button returned = schedule_button then
if res's text returned as integer0 and res's text returned as integer23 then
set_plist_hour(res's text returned) pmset_wake(res's text returned) display dialog "毎日" & plist_hour() & "時にアップデートを実行します。" buttons "OK" default button 1 with icon 1 giving up after 3
else if res's text returned = "" then
rm_plist() display dialog "予約が解除されました。" buttons "OK" default button 1 with icon 1 giving up after 3
end if
end if
if res's button returned = update_button then
run script POSIX file script_path() end if
end run




--GUIスクリプティングを有効にする
on GUI_scripting_check() tell application "System Events"
repeat until UI elements enabled
activate
"GUIスクリプティングを利用します。\n「補助装置にアクセスできるようにする」\nにチェックを入れて処理を続けますか?"
display dialog result buttons {"キャンセル", "チェックを入れて続ける"} default button 2 with icon note
set UI elements enabled to true
end repeat
end tell
end GUI_scripting_check

--launchd_plistの設定
on launchd_init() "[ -r " & launchd_plist() & " ] || ("
result & "cp " & original_plist() & space & launchd_plist() result & ";fpath=" & launchd_plist() result & ";defaults write \"${fpath%.*}\" ProgramArguments -array osascript " & script_path() result & ";launchctl load " & launchd_plist() result & ")"
do shell script result
end launchd_init

--launchd予約時間を返す
on plist_hour() do shell script "fpath=" & launchd_plist() & ";defaults read \"${fpath%.*}\" StartCalendarInterval|grep Hour|cut -d '=' -f 2|sed -e 's/[ ;]//g'"
end plist_hour

--launchd予約時間をセットする
on set_plist_hour(h) do shell script "fpath=" & launchd_plist() & ";defaults write \"${fpath%.*}\" StartCalendarInterval -dict Hour -int " & h & " Minute -int 0"
do shell script "launchctl unload " & launchd_plist() do shell script "launchctl load " & launchd_plist() end set_plist_hour

--wakeスケジュール(繰り返し)をセットする
on pmset_wake(h) do shell script "pmset repeat wake MTWRFSU " & h & ":00:00" with administrator privileges
end pmset_wake

--launchd_plistを削除する
on rm_plist() do shell script "launchctl unload " & launchd_plist() do shell script "[ -r " & launchd_plist() & " ] && rm " & launchd_plist() end rm_plist

--パスを返す
on original_plist() (path to resource "com.bebekoubou.iTunes_app_update.plist")'s POSIX path
end original_plist

on launchd_plist() "~/Library/LaunchAgents/com.bebekoubou.iTunes_app_update.plist"
end launchd_plist

on script_path() (((path to resource "Scripts") as text) & "iTunes_app_update.scpt")'s POSIX path
end script_path

launchd_plist

  • 毎日1時に実行するlaunchdの設定はファイルは以下。
  • $HOMEの部分は、ホームフォルダのパスに書き換える必要がある。
    • 但し、その処理もAppleScriptがするので、手動で書き換える必要なし。


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.bebekoubou.iTunes_app_update</string>
<key>ProgramArguments</key>
<array>
<string>osascript</string>
<string>$HOME/Library/Scripts/iTunes_app_update/iTunes_app_update.app/Contents/Resources/Scripts/iTunes_app_update.scpt</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>1</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</dict>
</plist>

ダウンロードと使い方

    • 年齢認証ダイアログを突破できない問題を修正しました。
    • 予約時間を変更できない問題を修正しました。
    • 除外アプリを指定できるようにしました。>>移動する
  • 事前準備:iTunesのStoreメニューからサインインしておく。(普通はサインイン済のはず)


  • iTunes_app_update.appを実行して、まずiTunes Appアップデートボタンを押して、本当に自動アップデートできるか試してみる。(自分の環境では動作する)
    • 日本語iTunes環境でのみ実行可能。


    • iTunes Storeのパスワードは保存しておく。(自分としては本意でないが、保存しておかないとアップデートが自動化されないので)


  • ここで自動アップデートされ、同期まで問題なく完了すれば、デフォルトで毎日1時に予約された自動アップデートも問題なく行われるはずなのだ。
  • MacBookは、モニタを閉じない状態で運用する。
  • スリープしていてもOK。時間になるとスリープから復帰してアップデートする。
  • MacBookiPhoneiPadなどは、同じWiFiネットワーク内に置いておく。

あとは、時が来ればアップデートが始まるのだ!

参考ページ

素晴らしいスクリプトに感謝です!(思うように操作できなくて、悩んでいる時に見つけました)