この記事の目的
iPhoneでは、通知をトリガーにして何か別の処理を行うなど、通知による自動化をすることは現状では出来ませんが、macOS 15からはiPhoneミラーリングによって、iPhoneの通知をMac端末へ取り込めるようになりました。
これにより、iPhoneの通知をトリガーにWebhookを送ってみるなどの自動化ができる気がしたので、本記事ではその方法を探ります。
前提条件
macOS 15.5を使用します。
本記事は公式にドキュメント化されていない内容であるため、本バージョン以外では動作しない可能性があります。
% sw_vers
ProductName: macOS
ProductVersion: 15.5
BuildVersion: 24F74
また、iPhoneミラーリングの設定を行い、通知が常にMacに対して転送される設定にしておきます。
macOSの通知センター
macOSでは、画面右上の通知センターに全ての通知がまとめられます。
macOS 15では、SQlite DBとして下記に通知が保存されます1。
~/Library/Group Containers/group.com.apple.usernoted/db2/db
DBから通知を取得するスクリプト2を実行してみたところ、
ここにiPhoneの通知も入ると思いきや、ここにはMac内で発生した通知しか入っていないことが分かりました。
iPhoneからの通知はどこに保存される?
~/Library/Group Containers
の中を眺めていると、group.com.apple.UserNotifications
という気になるディレクトリを見つけました。
このディレクトリの中にあるLibrary.plist
を見てみると、iPhone側のパッケージ名があったため、iPhoneミラーリングと関係がありそうです。
plistファイルはplutil
コマンドを使ってみていきます。
$ pwd
/Users/[user name]/Library/Group Containers/group.com.apple.UserNotifications/Library/UserNotifications/Remote/default
$ plutil -convert xml1 -o - ./Library.plist
plutilの出力:
[中略]
<string>com.apple.Health</string>
<string>jp.co.yahoo.YWeatherApp</string>
<string>com.apple.tips</string>
<string>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</string>
<string>YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY</string>
<string>ZZZZZZZZ-ZZZZ-ZZZZ-ZZZZ-ZZZZZZZZZZZZ</string>
[中略]
パッケージ名のあとパッケージのUUIDと思われるものが、パッケージ名のアイテム数と同じ数だけあります。
もし同じ順番だとすれば、jp.co.yahoo.YWeatherApp
のUUIDはYYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY
のはずです3。
/Users/[user name]/Library/Group Containers/group.com.apple.UserNotifications/Library/UserNotifications/Remote/default/YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY
に移動してみましょう。
中身は以下のようになっています。
$ ls -a
. Attachments Categories.plist
.. AttachmentsList.plist DeliveredNotifications.plist
通知の中身が入っていそうですね。DeliveredNotifications.plist
の中身を確認してみましょう。
$ plutil -convert xml1 -o - ./DeliveredNotifications.plist
ありました! DeliveredNotifications.plistを定期的に監視すれば、オートメーション化できそうですね
通知を処理する
ディレクトリの構造的には、iOS内で通知が保存されているディレクトリ(/private/var/mobile/Library/UserNotifications/
)と似ている印象です。
DeliveredNotifications.plist
をXMLに変換し、今回作成したPythonスクリプト(記事末尾に記載)に流すと、以下のような出力を得られます。
$ plutil -convert xml1 -o - ./DeliveredNotifications.plist > input.xml
$ pytnon3 process.py ./input.xml
Notification Time: 2025-06-21 14:49:50
Notification Text: 【動画ニュース】日曜日は関東で猛暑日も 北日本は週明けにかけ大雨のおそれ 来週は各地で梅雨空戻る(気象予報士・及川藍)
AttachmentURL: file:///Users/[user name]/Library/Group%20Containers/group.com.apple.UserNotifications/Library/UserNotifications/Remote/default/YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY/Attachments/[filename]
--------------------------------------------------
Notification Time: 2025-06-21 10:28:36
Notification Text: 【夏至】一年で最も昼の時間が長い日
冬至と比べてどれくらい長い?
AttachmentURL: file:///Users/[user name]/Library/Group%20Containers/group.com.apple.UserNotifications/Library/UserNotifications/Remote/default/YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY/Attachments/[filename]
--------------------------------------------------
Notification Time: 2025-06-21 08:01:41
....
AttachmentURLが存在する場合、通知に含まれる画像が取得できます。
アプリのアイコンも取得したい
バンドルIDがplistから取得できるため、最も楽なのはiTunes Search APIを使用して、アイコンを取得することだと思います。
BUNDLE_ID="jp.co.yahoo.YWeatherApp"
ICON_URL=$(curl -s "https://itunes.apple.com/lookup?bundleId=${BUNDLE_ID}&country=jp&entity=software" | jq -r '.results[0].artworkUrl512')
wget $ICON_URL
使用したスクリプト
import sys
import plistlib
import datetime
file_path = sys.argv[1]
with open(file_path, 'rb') as fp:
data = plistlib.load(fp)
objects = data['$objects']
def get_value(dict_obj, key_name):
try:
for i, key_ref in enumerate(dict_obj.get('NS.keys', [])):
key_string = objects[key_ref.get('CF$UID')]
if key_string == key_name:
value_uid = dict_obj.get('NS.objects', [])[i].get('CF$UID')
return objects[value_uid]
except (KeyError, ValueError, IndexError, TypeError):
return None
return None
for notif_ref in objects[1]['NS.objects']:
notif_obj = objects[notif_ref['CF$UID']]
time_str = "N/A"
try:
date_obj = get_value(notif_obj, "AppNotificationCreationDate")
cf_timestamp = date_obj.get('NS.time')
ref_date = datetime.datetime(2001, 1, 1, tzinfo=datetime.timezone.utc)
utc_time = ref_date + datetime.timedelta(seconds=cf_timestamp)
jst = datetime.timezone(datetime.timedelta(hours=9), name="JST")
local_time = utc_time.astimezone(jst)
time_str = local_time.strftime('%Y-%m-%d %H:%M:%S')
except (AttributeError, TypeError):
pass
msg = get_value(notif_obj, "AppNotificationMessage")
if not msg:
msg = get_value(notif_obj, "AppNotificationTitle")
msg = msg or "N/A"
url = "N/A"
try:
attachments = get_value(notif_obj, "AppNotificationAttachments")
attach_obj = objects[attachments['NS.objects'][0]['CF$UID']]
url_obj = get_value(attach_obj, 'AttachmentURL')
url = objects[url_obj['NS.relative']['CF$UID']]
except (AttributeError, KeyError, ValueError, IndexError, TypeError):
pass
print(f"Notification Time: {time_str}")
print(f"Notification Text: {msg}")
print(f"AttachmentURL: {url}")
print("--------------------------------------------------")
-
比較的単純なので推測で分かりますが、複雑な場合はNSKeyedUnarchiverを使用してデコードする必要があります ↩