Skip to content

Commit 0859a9c

Browse files
authored
Merge pull request #43 from photon-hq/feat/ENG-800
feat: support rich text formatting in iMessage kit
2 parents d65abf7 + 38c2451 commit 0859a9c

7 files changed

Lines changed: 315 additions & 158 deletions

File tree

README.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ If you're looking for the **free/open-source version**, please use **[iMessage K
3939
| [Send Messages](#send-messages) | Send text messages to any contact | `messages.sendMessage()` | [message-send.ts](./examples/message-send.ts) |
4040
| [Reply to Messages](#send-messages) | Reply inline to a specific message | `messages.sendMessage()` | [message-reply.ts](./examples/message-reply.ts) |
4141
| [Message Effects](#send-messages) | Send with effects (confetti, fireworks, etc.) | `messages.sendMessage()` | [message-effects.ts](./examples/message-effects.ts) |
42+
| [Text Styles](#text-styles--animations) | Bold, italic, underline, strikethrough | `messages.sendMessage()` | [message-styled.ts](./examples/message-styled.ts) |
43+
| [Text Animations](#text-styles--animations) | Per-character animations (shake, ripple, etc.)| `messages.sendMessage()` | [message-styled.ts](./examples/message-styled.ts) |
4244
| [Send Rich Links](#send-messages) | Send URLs with rich link previews | `messages.sendMessage()` | [message-rich-link.ts](./examples/message-rich-link.ts) |
4345
| [Schedule Messages](#scheduled-messages) | Send once or on a recurring schedule | `scheduledMessages.createScheduledMessage()` | [scheduled-message-once.ts](./examples/scheduled-message-once.ts) |
4446
| [Unsend Messages](#unsend-messages) | Retract a sent message | `messages.unsendMessage()` | [message-unsend.ts](./examples/message-unsend.ts) |
@@ -207,6 +209,71 @@ await sdk.messages.sendMessage({
207209

208210
> Example: [message-effects.ts](./examples/message-effects.ts)
209211
212+
### Text Styles & Animations
213+
214+
Apply per-range text formatting and whole-message character animations (requires Private API):
215+
216+
```typescript
217+
// Bold a portion of text
218+
await sdk.messages.sendMessage({
219+
chatGuid: "iMessage;-;+1234567890",
220+
message: "This is bold text",
221+
textStyles: [{ start: 8, end: 12, bold: true }],
222+
});
223+
224+
// Multiple styles in one message
225+
await sdk.messages.sendMessage({
226+
chatGuid: "iMessage;-;+1234567890",
227+
message: "Bold here, italic there",
228+
textStyles: [
229+
{ start: 0, end: 9, bold: true },
230+
{ start: 11, end: 23, italic: true },
231+
],
232+
});
233+
234+
// Character animation (applies to whole message)
235+
await sdk.messages.sendMessage({
236+
chatGuid: "iMessage;-;+1234567890",
237+
message: "Ripple wave!",
238+
textAnimation: "ripple",
239+
});
240+
241+
// Combine all three layers
242+
await sdk.messages.sendMessage({
243+
chatGuid: "iMessage;-;+1234567890",
244+
message: "Bold shaking fireworks!",
245+
textStyles: [{ start: 0, end: 4, bold: true }],
246+
textAnimation: "shake",
247+
effectId: "com.apple.messages.effect.CKFireworksEffect",
248+
});
249+
```
250+
251+
**Text Style Properties** (per range):
252+
253+
| Property | Type | Description |
254+
| --------------- | ------- | -------------- |
255+
| `start` | number | Start index |
256+
| `end` | number | End index |
257+
| `bold` | boolean | Bold |
258+
| `italic` | boolean | Italic |
259+
| `underline` | boolean | Underline |
260+
| `strikethrough` | boolean | Strikethrough |
261+
262+
**Text Animations** (whole message):
263+
264+
| `textAnimation` | Description |
265+
| --------------- | ----------- |
266+
| `"big"` | Big |
267+
| `"small"` | Small |
268+
| `"shake"` | Shake |
269+
| `"nod"` | Nod |
270+
| `"explode"` | Explode |
271+
| `"ripple"` | Ripple |
272+
| `"bloom"` | Bloom |
273+
| `"jitter"` | Jitter |
274+
275+
> Example: [message-styled.ts](./examples/message-styled.ts)
276+
210277
### Query Messages
211278

212279
```typescript
@@ -1130,8 +1197,9 @@ bun run examples/<filename>.ts
11301197
| [message-unsend.ts](./examples/message-unsend.ts) | Unsend messages |
11311198
| [message-edit.ts](./examples/message-edit.ts) | Edit messages |
11321199
| [message-reaction.ts](./examples/message-reaction.ts) | Send Tapbacks |
1133-
| [message-effects.ts](./examples/message-effects.ts) | Message effects |
1134-
| [message-search.ts](./examples/message-search.ts) | Search messages |
1200+
| [message-effects.ts](./examples/message-effects.ts) | Message effects |
1201+
| [message-styled.ts](./examples/message-styled.ts) | Text styles & animations |
1202+
| [message-search.ts](./examples/message-search.ts) | Search messages |
11351203
| [message-history.ts](./examples/message-history.ts) | Message history |
11361204
| [message-destination-caller-id.ts](./examples/message-destination-caller-id.ts) | Destination caller ID |
11371205

examples/message-effects.ts

Lines changed: 50 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,64 @@
1+
import type { BubbleEffect } from "../types";
12
import { createSDK, handleError } from "./utils";
23

3-
const CHAT_GUID = process.env.CHAT_GUID || "any;-;+13322593374";
4-
5-
const MESSAGE_EFFECTS = {
6-
confetti: "com.apple.messages.effect.CKConfettiEffect",
7-
lasers: "com.apple.messages.effect.CKHappyBirthdayEffect",
8-
fireworks: "com.apple.messages.effect.CKFireworksEffect",
9-
balloons: "com.apple.messages.effect.CKBalloonEffect",
10-
hearts: "com.apple.messages.effect.CKHeartEffect",
11-
shootingStar: "com.apple.messages.effect.CKShootingStarEffect",
12-
celebration: "com.apple.messages.effect.CKSparklesEffect",
13-
echo: "com.apple.messages.effect.CKEchoEffect",
14-
spotlight: "com.apple.messages.effect.CKSpotlightEffect",
15-
gentle: "com.apple.MobileSMS.expressivesend.gentle",
16-
loud: "com.apple.MobileSMS.expressivesend.loud",
17-
slam: "com.apple.MobileSMS.expressivesend.impact",
18-
invisible_ink: "com.apple.MobileSMS.expressivesend.invisibleink",
19-
} as const;
4+
const CHAT_GUID = process.env.CHAT_GUID || "any;-;+1234567890";
5+
6+
const BUBBLE_EFFECTS: BubbleEffect[] = [
7+
"confetti",
8+
"lasers",
9+
"fireworks",
10+
"balloons",
11+
"hearts",
12+
"shootingStar",
13+
"celebration",
14+
"echo",
15+
"spotlight",
16+
"gentle",
17+
"loud",
18+
"slam",
19+
"invisibleInk",
20+
];
21+
22+
const EFFECT_MESSAGES: Record<BubbleEffect, string> = {
23+
confetti: "Happy Birthday!",
24+
lasers: "Pew pew pew!",
25+
fireworks: "Celebration time!",
26+
balloons: "Congratulations!",
27+
hearts: "I love you!",
28+
shootingStar: "Make a wish!",
29+
celebration: "Amazing!",
30+
echo: "Hello hello hello...",
31+
spotlight: "Look at me!",
32+
gentle: "Shh...",
33+
loud: "IMPORTANT MESSAGE!",
34+
slam: "BAM!",
35+
invisibleInk: "Secret message!",
36+
};
2037

2138
async function main() {
2239
const sdk = createSDK();
2340

2441
sdk.on("ready", async () => {
25-
console.log("Message effects example (requires Private API)\n");
42+
console.log("Bubble effect examples (requires Private API)\n");
2643

2744
try {
28-
// Confetti effect
29-
const confettiMessage = await sdk.messages.sendMessage({
30-
chatGuid: CHAT_GUID,
31-
message: "Happy Birthday!",
32-
effectId: MESSAGE_EFFECTS.confetti,
33-
});
34-
console.log(`confetti: ${confettiMessage.guid}`);
35-
36-
await new Promise((resolve) => setTimeout(resolve, 5000));
37-
38-
// Lasers effect
39-
const lasersMessage = await sdk.messages.sendMessage({
40-
chatGuid: CHAT_GUID,
41-
message: "Pew pew pew!",
42-
effectId: MESSAGE_EFFECTS.lasers,
43-
});
44-
console.log(`lasers: ${lasersMessage.guid}`);
45-
46-
await new Promise((resolve) => setTimeout(resolve, 5000));
47-
48-
// Fireworks effect
49-
const fireworksMessage = await sdk.messages.sendMessage({
50-
chatGuid: CHAT_GUID,
51-
message: "Celebration time!",
52-
effectId: MESSAGE_EFFECTS.fireworks,
53-
});
54-
console.log(`fireworks: ${fireworksMessage.guid}`);
55-
56-
await new Promise((resolve) => setTimeout(resolve, 5000));
57-
58-
// Balloons effect
59-
const balloonsMessage = await sdk.messages.sendMessage({
60-
chatGuid: CHAT_GUID,
61-
message: "Congratulations!",
62-
effectId: MESSAGE_EFFECTS.balloons,
63-
});
64-
console.log(`balloons: ${balloonsMessage.guid}`);
65-
66-
await new Promise((resolve) => setTimeout(resolve, 5000));
67-
68-
// Hearts effect
69-
const heartsMessage = await sdk.messages.sendMessage({
70-
chatGuid: CHAT_GUID,
71-
message: "I love you!",
72-
effectId: MESSAGE_EFFECTS.hearts,
73-
});
74-
console.log(`hearts: ${heartsMessage.guid}`);
75-
76-
await new Promise((resolve) => setTimeout(resolve, 5000));
77-
78-
// Shooting star effect
79-
const shootingStarMessage = await sdk.messages.sendMessage({
80-
chatGuid: CHAT_GUID,
81-
message: "Make a wish!",
82-
effectId: MESSAGE_EFFECTS.shootingStar,
83-
});
84-
console.log(`shootingStar: ${shootingStarMessage.guid}`);
85-
86-
await new Promise((resolve) => setTimeout(resolve, 5000));
87-
88-
// Celebration effect
89-
const celebrationMessage = await sdk.messages.sendMessage({
90-
chatGuid: CHAT_GUID,
91-
message: "Amazing!",
92-
effectId: MESSAGE_EFFECTS.celebration,
93-
});
94-
console.log(`celebration: ${celebrationMessage.guid}`);
95-
96-
await new Promise((resolve) => setTimeout(resolve, 5000));
97-
98-
// Echo effect
99-
const echoMessage = await sdk.messages.sendMessage({
100-
chatGuid: CHAT_GUID,
101-
message: "Hello hello hello...",
102-
effectId: MESSAGE_EFFECTS.echo,
103-
});
104-
console.log(`echo: ${echoMessage.guid}`);
105-
106-
await new Promise((resolve) => setTimeout(resolve, 5000));
107-
108-
// Spotlight effect
109-
const spotlightMessage = await sdk.messages.sendMessage({
110-
chatGuid: CHAT_GUID,
111-
message: "Look at me!",
112-
effectId: MESSAGE_EFFECTS.spotlight,
113-
});
114-
console.log(`spotlight: ${spotlightMessage.guid}`);
115-
116-
await new Promise((resolve) => setTimeout(resolve, 5000));
117-
118-
// Gentle effect
119-
const gentleMessage = await sdk.messages.sendMessage({
120-
chatGuid: CHAT_GUID,
121-
message: "Shh...",
122-
effectId: MESSAGE_EFFECTS.gentle,
123-
});
124-
console.log(`gentle: ${gentleMessage.guid}`);
125-
126-
await new Promise((resolve) => setTimeout(resolve, 5000));
127-
128-
// Loud effect
129-
const loudMessage = await sdk.messages.sendMessage({
130-
chatGuid: CHAT_GUID,
131-
message: "IMPORTANT MESSAGE!",
132-
effectId: MESSAGE_EFFECTS.loud,
133-
});
134-
console.log(`loud: ${loudMessage.guid}`);
135-
136-
await new Promise((resolve) => setTimeout(resolve, 5000));
137-
138-
// Slam effect
139-
const slamMessage = await sdk.messages.sendMessage({
140-
chatGuid: CHAT_GUID,
141-
message: "BAM!",
142-
effectId: MESSAGE_EFFECTS.slam,
143-
});
144-
console.log(`slam: ${slamMessage.guid}`);
145-
146-
await new Promise((resolve) => setTimeout(resolve, 5000));
147-
148-
// Invisible ink effect
149-
const invisibleMessage = await sdk.messages.sendMessage({
150-
chatGuid: CHAT_GUID,
151-
message: "Secret message!",
152-
effectId: MESSAGE_EFFECTS.invisible_ink,
153-
});
154-
console.log(`invisible ink: ${invisibleMessage.guid}`);
155-
156-
console.log("\nAvailable effects:");
157-
console.log(JSON.stringify(MESSAGE_EFFECTS, null, 2));
45+
for (const effect of BUBBLE_EFFECTS) {
46+
const msg = await sdk.messages.sendMessage({
47+
chatGuid: CHAT_GUID,
48+
message: EFFECT_MESSAGES[effect],
49+
bubbleEffect: effect,
50+
});
51+
console.log(`${effect}: ${msg.guid}`);
52+
await new Promise((resolve) => setTimeout(resolve, 5000));
53+
}
54+
55+
console.log("\nAvailable bubble effects:");
56+
for (const effect of BUBBLE_EFFECTS) {
57+
console.log(` ${effect}`);
58+
}
15859
} catch (error) {
15960
handleError(error, "Failed to send message with effect");
160-
console.log("\nNote: Effects require Private API to be enabled");
61+
console.log("\nNote: Bubble effects require Private API to be enabled");
16162
}
16263

16364
await sdk.close();

0 commit comments

Comments
 (0)