fix: reject unsupported custom emoji reactions
This commit is contained in:
parent
715a75fb4e
commit
df2d928ff0
@ -21,6 +21,7 @@
|
||||
- fix: confirm standard tapback reaction selection in Messages automation (#53, thanks @PeterRosdahl)
|
||||
- fix: gate RPC watch reaction metadata on `include_reactions`, not `attachments` (#82)
|
||||
- fix: dedupe URL balloon preview duplicates in watch stream without cross-chat/schema regressions (#64, thanks @lesaai)
|
||||
- fix: reject unsupported custom emoji reaction sends instead of emitting a no-op AppleScript path (#55)
|
||||
- fix: normalize IMCore typing chat lookup across `iMessage`, `SMS`, and `any` prefixes (#51, #54, #56, #58)
|
||||
- docs: document macOS 26 advanced IMCore injection limits (#60)
|
||||
- fix: report macOS 26/Tahoe IMCore typing entitlement failures as advanced-feature setup errors (#60)
|
||||
|
||||
@ -91,6 +91,10 @@ imsg launch
|
||||
imsg typing --to "+14155551212" --duration 5s
|
||||
```
|
||||
|
||||
`imsg react` sends only the standard tapbacks exposed reliably through
|
||||
Messages.app automation. Custom emoji tapbacks can be read from history/watch
|
||||
output, but are not sent by the CLI.
|
||||
|
||||
## Attachment notes
|
||||
`--attachments` prints per-attachment lines with name, MIME, missing flag, and resolved path (tilde expanded). By default only metadata is shown; files aren’t copied.
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ public enum IMsgError: LocalizedError, Sendable {
|
||||
case appleScriptFailure(String)
|
||||
case typingIndicatorFailed(String)
|
||||
case invalidReaction(String)
|
||||
case unsupportedReaction(String)
|
||||
case chatNotFound(chatID: Int64)
|
||||
|
||||
public var errorDescription: String? {
|
||||
@ -45,8 +46,9 @@ public enum IMsgError: LocalizedError, Sendable {
|
||||
Invalid reaction: \(value)
|
||||
|
||||
Valid reactions: love, like, dislike, laugh, emphasis, question
|
||||
Or use an emoji for custom reactions (e.g., 🎉)
|
||||
"""
|
||||
case .unsupportedReaction(let message):
|
||||
return "Unsupported reaction: \(message)"
|
||||
case .chatNotFound(let chatID):
|
||||
return "Chat not found: \(chatID)"
|
||||
}
|
||||
|
||||
@ -16,7 +16,9 @@ enum ReactCommand {
|
||||
|
||||
Reaction types:
|
||||
love (❤️), like (👍), dislike (👎), laugh (😂), emphasis (‼️), question (❓)
|
||||
Or any single emoji for custom reactions (iOS 17+ / macOS 14+)
|
||||
|
||||
Custom emoji tapbacks can be read from history/watch output, but cannot be
|
||||
sent reliably through Messages.app AppleScript automation.
|
||||
""",
|
||||
signature: CommandSignatures.withRuntimeFlags(
|
||||
CommandSignature(
|
||||
@ -24,7 +26,7 @@ enum ReactCommand {
|
||||
.make(label: "chatID", names: [.long("chat-id")], help: "chat rowid to react in"),
|
||||
.make(
|
||||
label: "reaction", names: [.long("reaction"), .short("r")],
|
||||
help: "reaction type: love, like, dislike, laugh, emphasis, question, or emoji"),
|
||||
help: "reaction type: love, like, dislike, laugh, emphasis, question"),
|
||||
],
|
||||
flags: []
|
||||
)
|
||||
@ -32,7 +34,6 @@ enum ReactCommand {
|
||||
usageExamples: [
|
||||
"imsg react --chat-id 1 --reaction like",
|
||||
"imsg react --chat-id 1 -r love",
|
||||
"imsg react --chat-id 1 -r 🎉",
|
||||
]
|
||||
) { values, runtime in
|
||||
try await run(values: values, runtime: runtime)
|
||||
@ -58,6 +59,12 @@ enum ReactCommand {
|
||||
if case .custom(let emoji) = reactionType, !isSingleEmoji(emoji) {
|
||||
throw IMsgError.invalidReaction(reactionString)
|
||||
}
|
||||
if case .custom(let emoji) = reactionType {
|
||||
throw IMsgError.unsupportedReaction(
|
||||
"custom emoji tapback '\(emoji)' cannot be sent by Messages.app "
|
||||
+ "AppleScript automation; use love, like, dislike, laugh, emphasis, or question."
|
||||
)
|
||||
}
|
||||
|
||||
// Get chat info for the GUID
|
||||
let dbPath = values.option("db") ?? MessageStore.defaultPath
|
||||
@ -103,40 +110,11 @@ enum ReactCommand {
|
||||
case .laugh: keyNumber = 4
|
||||
case .emphasis: keyNumber = 5
|
||||
case .question: keyNumber = 6
|
||||
case .custom:
|
||||
let script = """
|
||||
on run argv
|
||||
set chatGUID to item 1 of argv
|
||||
set chatLookup to item 2 of argv
|
||||
set customEmoji to item 3 of argv
|
||||
|
||||
tell application "Messages"
|
||||
activate
|
||||
set targetChat to chat id chatGUID
|
||||
end tell
|
||||
|
||||
delay 0.3
|
||||
|
||||
tell application "System Events"
|
||||
tell process "Messages"
|
||||
keystroke "f" using command down
|
||||
delay 0.15
|
||||
keystroke "a" using command down
|
||||
keystroke chatLookup
|
||||
delay 0.25
|
||||
key code 36
|
||||
delay 0.35
|
||||
keystroke "t" using command down
|
||||
delay 0.2
|
||||
keystroke customEmoji
|
||||
delay 0.1
|
||||
key code 36
|
||||
end tell
|
||||
end tell
|
||||
end run
|
||||
"""
|
||||
try appleScriptRunner(script, [chatGUID, chatLookup, reactionType.emoji])
|
||||
return
|
||||
case .custom(let emoji):
|
||||
throw IMsgError.unsupportedReaction(
|
||||
"custom emoji tapback '\(emoji)' cannot be sent by Messages.app "
|
||||
+ "AppleScript automation; use love, like, dislike, laugh, emphasis, or question."
|
||||
)
|
||||
}
|
||||
|
||||
let script = """
|
||||
|
||||
@ -60,7 +60,7 @@ func reactCommandBuildsParameterizedAppleScriptForStandardTapback() async throws
|
||||
}
|
||||
|
||||
@Test
|
||||
func reactCommandBuildsParameterizedAppleScriptForCustomEmoji() async throws {
|
||||
func reactCommandRejectsCustomEmojiSend() async throws {
|
||||
let path = try CommandTestDatabase.makePath()
|
||||
let values = ParsedValues(
|
||||
positional: [],
|
||||
@ -68,21 +68,25 @@ func reactCommandBuildsParameterizedAppleScriptForCustomEmoji() async throws {
|
||||
flags: []
|
||||
)
|
||||
let runtime = RuntimeOptions(parsedValues: values)
|
||||
var capturedScript = ""
|
||||
var capturedArguments: [String] = []
|
||||
_ = try await StdoutCapture.capture {
|
||||
do {
|
||||
try await ReactCommand.run(
|
||||
values: values,
|
||||
runtime: runtime,
|
||||
appleScriptRunner: { source, arguments in
|
||||
capturedScript = source
|
||||
capturedArguments = arguments
|
||||
appleScriptRunner: { _, _ in
|
||||
#expect(Bool(false))
|
||||
}
|
||||
)
|
||||
#expect(Bool(false))
|
||||
} catch let error as IMsgError {
|
||||
switch error {
|
||||
case .unsupportedReaction(let message):
|
||||
#expect(message.contains("custom emoji tapback"))
|
||||
#expect(message.contains("AppleScript automation"))
|
||||
#expect(message.contains("love"))
|
||||
default:
|
||||
#expect(Bool(false))
|
||||
}
|
||||
} catch {
|
||||
#expect(Bool(false))
|
||||
}
|
||||
#expect(capturedArguments == ["iMessage;+;chat123", "Test Chat", "🎉"])
|
||||
#expect(capturedScript.contains("on run argv"))
|
||||
#expect(capturedScript.contains("keystroke customEmoji"))
|
||||
#expect(capturedScript.contains("key code 36"))
|
||||
#expect(capturedScript.contains("chat123") == false)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user