@mapier/imsg-sdk 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mapier/imsg-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "TypeScript SDK for iMessage automation on macOS: the Gateway contract, ImsgGateway (real), and a device-free FakeGateway.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,14 +37,37 @@
|
|
|
37
37
|
".": {
|
|
38
38
|
"types": "./dist/index.d.ts",
|
|
39
39
|
"import": "./dist/index.js"
|
|
40
|
-
}
|
|
40
|
+
},
|
|
41
|
+
"./types": {
|
|
42
|
+
"types": "./dist/types.d.ts",
|
|
43
|
+
"import": "./dist/types.js"
|
|
44
|
+
},
|
|
45
|
+
"./polls": {
|
|
46
|
+
"types": "./dist/polls.d.ts",
|
|
47
|
+
"import": "./dist/polls.js"
|
|
48
|
+
},
|
|
49
|
+
"./gateway/types": {
|
|
50
|
+
"types": "./dist/gateway/types.d.ts",
|
|
51
|
+
"import": "./dist/gateway/types.js"
|
|
52
|
+
},
|
|
53
|
+
"./gateway/fake": {
|
|
54
|
+
"types": "./dist/gateway/fake.d.ts",
|
|
55
|
+
"import": "./dist/gateway/fake.js"
|
|
56
|
+
},
|
|
57
|
+
"./gateway/portable-chat": {
|
|
58
|
+
"types": "./dist/gateway/portable-chat.d.ts",
|
|
59
|
+
"import": "./dist/gateway/portable-chat.js"
|
|
60
|
+
},
|
|
61
|
+
"./package.json": "./package.json"
|
|
41
62
|
},
|
|
42
63
|
"files": [
|
|
43
|
-
"dist"
|
|
64
|
+
"dist",
|
|
65
|
+
"scripts/*.applescript"
|
|
44
66
|
],
|
|
45
67
|
"scripts": {
|
|
46
68
|
"build": "tsc -p tsconfig.build.json",
|
|
47
69
|
"test": "tsx --test tests/*.test.ts",
|
|
70
|
+
"pack:smoke": "tsx scripts/pack-smoke.ts",
|
|
48
71
|
"typecheck": "tsc --noEmit",
|
|
49
72
|
"lint": "biome check .",
|
|
50
73
|
"format": "biome check --write .",
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
-- Create an iMessage group chat by driving Messages.app's compose window.
|
|
2
|
+
-- The AppleScript group-chat API died in Big Sur; this is the same System
|
|
3
|
+
-- Events / Accessibility path `imsg react` uses. Needs an unlocked GUI
|
|
4
|
+
-- session + Accessibility permission for the invoking terminal.
|
|
5
|
+
--
|
|
6
|
+
-- Usage:
|
|
7
|
+
-- osascript scripts/create-group.applescript "+14155551212" "+16505551212" "first message"
|
|
8
|
+
--
|
|
9
|
+
-- Last argument is the first message (sending it is what creates the thread);
|
|
10
|
+
-- everything before it is a recipient (E.164 or iMessage email). Two or more
|
|
11
|
+
-- recipients = group. Recipients should be iMessage-reachable (blue), or the
|
|
12
|
+
-- thread degrades to SMS/MMS.
|
|
13
|
+
--
|
|
14
|
+
-- The message body is pasted via the clipboard (set the clipboard + Cmd+V),
|
|
15
|
+
-- not keystroke-typed — keystroke typing passes through Messages autocorrect
|
|
16
|
+
-- and mangles it ("mapi" -> "maxi"). Recipient handles stay keystroke-typed:
|
|
17
|
+
-- they're digits/short strings, autocorrect-safe, and need the per-character
|
|
18
|
+
-- autocomplete-chip behavior anyway.
|
|
19
|
+
--
|
|
20
|
+
-- Known-fragile keystrokes (tweak here if the smoke test fails):
|
|
21
|
+
-- * Return after each recipient commits the autocomplete chip.
|
|
22
|
+
-- * Tab moves focus from the To: field to the message body.
|
|
23
|
+
|
|
24
|
+
on run argv
|
|
25
|
+
set argc to count of argv
|
|
26
|
+
if argc < 3 then
|
|
27
|
+
error "usage: create-group.applescript <handle1> <handle2> [more handles...] <first message>"
|
|
28
|
+
end if
|
|
29
|
+
set msgText to item argc of argv
|
|
30
|
+
set recipients to items 1 thru (argc - 1) of argv
|
|
31
|
+
|
|
32
|
+
set the clipboard to msgText
|
|
33
|
+
|
|
34
|
+
tell application "Messages" to activate
|
|
35
|
+
delay 1
|
|
36
|
+
|
|
37
|
+
tell application "System Events"
|
|
38
|
+
tell process "Messages"
|
|
39
|
+
set titleBeforeCompose to name of front window
|
|
40
|
+
keystroke "n" using {command down} -- new compose
|
|
41
|
+
delay 1
|
|
42
|
+
-- Compose guard: if Cmd+N failed to enter compose (sibling of the
|
|
43
|
+
-- react.applescript Cmd+T failure, observed live 2026-07-17), every
|
|
44
|
+
-- keystroke below would land in the FOCUSED CHAT's compose field and
|
|
45
|
+
-- each Return would send it — recipient handles texted to whatever
|
|
46
|
+
-- thread is open. Entering compose retitles the window (chat title ->
|
|
47
|
+
-- "Messages" on macOS 26); an unchanged title means no compose, so
|
|
48
|
+
-- abort before typing anything.
|
|
49
|
+
if (name of front window) is titleBeforeCompose then
|
|
50
|
+
error "create-group.applescript: compose window did not open (window title unchanged) — nothing typed, no message sent"
|
|
51
|
+
end if
|
|
52
|
+
repeat with r in recipients
|
|
53
|
+
keystroke (r as text)
|
|
54
|
+
delay 1 -- let autocomplete settle
|
|
55
|
+
key code 36 -- Return: commit recipient chip
|
|
56
|
+
delay 1
|
|
57
|
+
end repeat
|
|
58
|
+
key code 48 -- Tab: To: field -> message body
|
|
59
|
+
delay 0.5
|
|
60
|
+
keystroke "v" using {command down} -- paste msgText, autocorrect-safe
|
|
61
|
+
delay 0.5
|
|
62
|
+
key code 36 -- Return: send (this creates the group thread)
|
|
63
|
+
end tell
|
|
64
|
+
end tell
|
|
65
|
+
end run
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
-- Tapback the most recent incoming message in a DM — our replacement for
|
|
2
|
+
-- `imsg react`, whose Cmd+F search-select race fires Cmd+T into whatever
|
|
3
|
+
-- chat happens to be focused (observed live 2026-07-02 tapbacking an
|
|
4
|
+
-- unrelated thread; its dance also simply fails to switch threads on
|
|
5
|
+
-- macOS 26 Messages). Differences:
|
|
6
|
+
-- 1. navigation via the OS-routed sms: URL scheme — addressed by handle,
|
|
7
|
+
-- no search UI, no race, no locale dependence
|
|
8
|
+
-- 2. focused-window title check before Cmd+T as defense-in-depth: titles
|
|
9
|
+
-- with digits must match the handle; Siri-suggested contact-name titles
|
|
10
|
+
-- ("Maybe: Mark Xiong") carry no digits and pass — the URL routing
|
|
11
|
+
-- already pinned the thread by handle
|
|
12
|
+
--
|
|
13
|
+
-- argv: 1 = handle (e.g. +17738867338), 2 = tapback key 1-6
|
|
14
|
+
-- (1 love, 2 like, 3 dislike, 4 laugh, 5 emphasis, 6 question)
|
|
15
|
+
-- DM-only. The caller (ImsgGateway.react) verifies the tapback actually
|
|
16
|
+
-- landed by re-reading history; this script only guarantees no side effect
|
|
17
|
+
-- in a wrong thread.
|
|
18
|
+
|
|
19
|
+
on digitsOf(s)
|
|
20
|
+
set out to ""
|
|
21
|
+
repeat with c in characters of s
|
|
22
|
+
if c is in "0123456789" then set out to out & c
|
|
23
|
+
end repeat
|
|
24
|
+
return out
|
|
25
|
+
end digitsOf
|
|
26
|
+
|
|
27
|
+
on run argv
|
|
28
|
+
set chatLookup to item 1 of argv
|
|
29
|
+
set reactionKey to item 2 of argv
|
|
30
|
+
set wantedDigits to digitsOf(chatLookup)
|
|
31
|
+
if wantedDigits is "" then error "react.applescript: handle has no digits: " & chatLookup
|
|
32
|
+
|
|
33
|
+
open location "sms:" & chatLookup
|
|
34
|
+
delay 1.5
|
|
35
|
+
|
|
36
|
+
tell application "System Events"
|
|
37
|
+
tell process "Messages"
|
|
38
|
+
set frontmost to true
|
|
39
|
+
set winTitle to name of front window
|
|
40
|
+
end tell
|
|
41
|
+
end tell
|
|
42
|
+
|
|
43
|
+
set titleDigits to digitsOf(winTitle)
|
|
44
|
+
if titleDigits is not "" and titleDigits does not contain wantedDigits and wantedDigits does not contain titleDigits then
|
|
45
|
+
error "react.applescript: wrong chat focused ('" & winTitle & "', wanted " & chatLookup & ") — no tapback sent"
|
|
46
|
+
end if
|
|
47
|
+
|
|
48
|
+
tell application "System Events"
|
|
49
|
+
tell process "Messages"
|
|
50
|
+
keystroke "t" using command down
|
|
51
|
+
delay 0.4
|
|
52
|
+
keystroke reactionKey
|
|
53
|
+
delay 0.3
|
|
54
|
+
end tell
|
|
55
|
+
end tell
|
|
56
|
+
|
|
57
|
+
-- Compose-leak guard: when Cmd+T fails to open the tapback picker (observed
|
|
58
|
+
-- live 2026-07-17 with the display asleep), the reaction key lands in the
|
|
59
|
+
-- compose field and the Return below would SEND it as a text. The picker
|
|
60
|
+
-- never puts the typed digit into a focused text input, so a focused text
|
|
61
|
+
-- input containing the key means no picker — clear the field and abort.
|
|
62
|
+
set leaked to false
|
|
63
|
+
tell application "System Events"
|
|
64
|
+
tell process "Messages"
|
|
65
|
+
try
|
|
66
|
+
set fe to value of attribute "AXFocusedUIElement"
|
|
67
|
+
set feRole to role of fe
|
|
68
|
+
if feRole is "AXTextField" or feRole is "AXTextArea" then
|
|
69
|
+
set fv to ""
|
|
70
|
+
try
|
|
71
|
+
set fv to value of fe as text
|
|
72
|
+
end try
|
|
73
|
+
if fv contains reactionKey then set leaked to true
|
|
74
|
+
end if
|
|
75
|
+
end try
|
|
76
|
+
end tell
|
|
77
|
+
end tell
|
|
78
|
+
if leaked then
|
|
79
|
+
tell application "System Events"
|
|
80
|
+
tell process "Messages"
|
|
81
|
+
keystroke "a" using command down
|
|
82
|
+
delay 0.2
|
|
83
|
+
key code 51
|
|
84
|
+
end tell
|
|
85
|
+
end tell
|
|
86
|
+
error "react.applescript: tapback picker did not open — reaction key landed in the compose field (cleared); no tapback sent"
|
|
87
|
+
end if
|
|
88
|
+
|
|
89
|
+
tell application "System Events"
|
|
90
|
+
tell process "Messages"
|
|
91
|
+
key code 36
|
|
92
|
+
end tell
|
|
93
|
+
end tell
|
|
94
|
+
end run
|