0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

macOSでiPhoneミラーリングの通知をスクリプトで取得したい

Last updated at Posted at 2025-06-21

この記事の目的

iPhoneでは、通知をトリガーにして何か別の処理を行うなど、通知による自動化をすることは現状では出来ませんが、macOS 15からはiPhoneミラーリングによって、iPhoneの通知をMac端末へ取り込めるようになりました。
これにより、iPhoneの通知をトリガーにWebhookを送ってみるなどの自動化ができる気がしたので、本記事ではその方法を探ります。
image.png

前提条件

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

plutilの出力:
image.png

ありました! 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

使用したスクリプト

process.py
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("--------------------------------------------------")
  1. https://x.com/theevilbit/status/1811758367045537990

  2. https://x.com/theevilbit/status/1828368209554162078

  3. 比較的単純なので推測で分かりますが、複雑な場合はNSKeyedUnarchiverを使用してデコードする必要があります

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?