diff --git a/README.md b/README.md index 3d5cc7c..978dfb2 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ Advanced iMessage Kit is a full-featured iMessage SDK for **reading**, **sending | [Send Messages](#send-messages) | Send text messages to any contact | `messages.sendMessage()` | [message-send.ts](./examples/message-send.ts) | | [Reply to Messages](#send-messages) | Reply inline to a specific message | `messages.sendMessage()` | [message-reply.ts](./examples/message-reply.ts) | | [Message Effects](#send-messages) | Send with effects (confetti, fireworks, etc.) | `messages.sendMessage()` | [message-effects.ts](./examples/message-effects.ts) | +| [Text Styles](#text-styles--animations) | Bold, italic, underline, strikethrough | `messages.sendMessage()` | [message-styled.ts](./examples/message-styled.ts) | +| [Text Animations](#text-styles--animations) | Per-character animations (shake, ripple, etc.)| `messages.sendMessage()` | [message-styled.ts](./examples/message-styled.ts) | | [Send Rich Links](#send-messages) | Send URLs with rich link previews | `messages.sendMessage()` | [message-rich-link.ts](./examples/message-rich-link.ts) | | [Schedule Messages](#scheduled-messages) | Send once or on a recurring schedule | `scheduledMessages.createScheduledMessage()` | [scheduled-message-once.ts](./examples/scheduled-message-once.ts) | | [Unsend Messages](#unsend-messages) | Retract a sent message | `messages.unsendMessage()` | [message-unsend.ts](./examples/message-unsend.ts) | @@ -191,6 +193,71 @@ await sdk.messages.sendMessage({ > Example: [message-effects.ts](./examples/message-effects.ts) +### Text Styles & Animations + +Apply per-range text formatting and whole-message character animations (requires Private API): + +```typescript +// Bold a portion of text +await sdk.messages.sendMessage({ + chatGuid: "iMessage;-;+1234567890", + message: "This is bold text", + textStyles: [{ start: 8, end: 12, bold: true }], +}); + +// Multiple styles in one message +await sdk.messages.sendMessage({ + chatGuid: "iMessage;-;+1234567890", + message: "Bold here, italic there", + textStyles: [ + { start: 0, end: 9, bold: true }, + { start: 11, end: 23, italic: true }, + ], +}); + +// Character animation (applies to whole message) +await sdk.messages.sendMessage({ + chatGuid: "iMessage;-;+1234567890", + message: "Ripple wave!", + textAnimation: "ripple", +}); + +// Combine all three layers +await sdk.messages.sendMessage({ + chatGuid: "iMessage;-;+1234567890", + message: "Bold shaking fireworks!", + textStyles: [{ start: 0, end: 4, bold: true }], + textAnimation: "shake", + effectId: "com.apple.messages.effect.CKFireworksEffect", +}); +``` + +**Text Style Properties** (per range): + +| Property | Type | Description | +| --------------- | ------- | -------------- | +| `start` | number | Start index | +| `end` | number | End index | +| `bold` | boolean | Bold | +| `italic` | boolean | Italic | +| `underline` | boolean | Underline | +| `strikethrough` | boolean | Strikethrough | + +**Text Animations** (whole message): + +| `textAnimation` | Description | +| --------------- | ----------- | +| `"big"` | Big | +| `"small"` | Small | +| `"shake"` | Shake | +| `"nod"` | Nod | +| `"explode"` | Explode | +| `"ripple"` | Ripple | +| `"bloom"` | Bloom | +| `"jitter"` | Jitter | + +> Example: [message-styled.ts](./examples/message-styled.ts) + ### Query Messages ```typescript @@ -1114,8 +1181,9 @@ bun run examples/.ts | [message-unsend.ts](./examples/message-unsend.ts) | Unsend messages | | [message-edit.ts](./examples/message-edit.ts) | Edit messages | | [message-reaction.ts](./examples/message-reaction.ts) | Send Tapbacks | -| [message-effects.ts](./examples/message-effects.ts) | Message effects | -| [message-search.ts](./examples/message-search.ts) | Search messages | +| [message-effects.ts](./examples/message-effects.ts) | Message effects | +| [message-styled.ts](./examples/message-styled.ts) | Text styles & animations | +| [message-search.ts](./examples/message-search.ts) | Search messages | | [message-history.ts](./examples/message-history.ts) | Message history | | [message-destination-caller-id.ts](./examples/message-destination-caller-id.ts) | Destination caller ID | diff --git a/examples/message-effects.ts b/examples/message-effects.ts index a9c71ec..d86de32 100644 --- a/examples/message-effects.ts +++ b/examples/message-effects.ts @@ -1,163 +1,64 @@ +import type { BubbleEffect } from "../types"; import { createSDK, handleError } from "./utils"; -const CHAT_GUID = process.env.CHAT_GUID || "any;-;+13322593374"; - -const MESSAGE_EFFECTS = { - confetti: "com.apple.messages.effect.CKConfettiEffect", - lasers: "com.apple.messages.effect.CKHappyBirthdayEffect", - fireworks: "com.apple.messages.effect.CKFireworksEffect", - balloons: "com.apple.messages.effect.CKBalloonEffect", - hearts: "com.apple.messages.effect.CKHeartEffect", - shootingStar: "com.apple.messages.effect.CKShootingStarEffect", - celebration: "com.apple.messages.effect.CKSparklesEffect", - echo: "com.apple.messages.effect.CKEchoEffect", - spotlight: "com.apple.messages.effect.CKSpotlightEffect", - gentle: "com.apple.MobileSMS.expressivesend.gentle", - loud: "com.apple.MobileSMS.expressivesend.loud", - slam: "com.apple.MobileSMS.expressivesend.impact", - invisible_ink: "com.apple.MobileSMS.expressivesend.invisibleink", -} as const; +const CHAT_GUID = process.env.CHAT_GUID || "any;-;+1234567890"; + +const BUBBLE_EFFECTS: BubbleEffect[] = [ + "confetti", + "lasers", + "fireworks", + "balloons", + "hearts", + "shootingStar", + "celebration", + "echo", + "spotlight", + "gentle", + "loud", + "slam", + "invisibleInk", +]; + +const EFFECT_MESSAGES: Record = { + confetti: "Happy Birthday!", + lasers: "Pew pew pew!", + fireworks: "Celebration time!", + balloons: "Congratulations!", + hearts: "I love you!", + shootingStar: "Make a wish!", + celebration: "Amazing!", + echo: "Hello hello hello...", + spotlight: "Look at me!", + gentle: "Shh...", + loud: "IMPORTANT MESSAGE!", + slam: "BAM!", + invisibleInk: "Secret message!", +}; async function main() { const sdk = createSDK(); sdk.on("ready", async () => { - console.log("Message effects example (requires Private API)\n"); + console.log("Bubble effect examples (requires Private API)\n"); try { - // Confetti effect - const confettiMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Happy Birthday!", - effectId: MESSAGE_EFFECTS.confetti, - }); - console.log(`confetti: ${confettiMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Lasers effect - const lasersMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Pew pew pew!", - effectId: MESSAGE_EFFECTS.lasers, - }); - console.log(`lasers: ${lasersMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Fireworks effect - const fireworksMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Celebration time!", - effectId: MESSAGE_EFFECTS.fireworks, - }); - console.log(`fireworks: ${fireworksMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Balloons effect - const balloonsMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Congratulations!", - effectId: MESSAGE_EFFECTS.balloons, - }); - console.log(`balloons: ${balloonsMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Hearts effect - const heartsMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "I love you!", - effectId: MESSAGE_EFFECTS.hearts, - }); - console.log(`hearts: ${heartsMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Shooting star effect - const shootingStarMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Make a wish!", - effectId: MESSAGE_EFFECTS.shootingStar, - }); - console.log(`shootingStar: ${shootingStarMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Celebration effect - const celebrationMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Amazing!", - effectId: MESSAGE_EFFECTS.celebration, - }); - console.log(`celebration: ${celebrationMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Echo effect - const echoMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Hello hello hello...", - effectId: MESSAGE_EFFECTS.echo, - }); - console.log(`echo: ${echoMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Spotlight effect - const spotlightMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Look at me!", - effectId: MESSAGE_EFFECTS.spotlight, - }); - console.log(`spotlight: ${spotlightMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Gentle effect - const gentleMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Shh...", - effectId: MESSAGE_EFFECTS.gentle, - }); - console.log(`gentle: ${gentleMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Loud effect - const loudMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "IMPORTANT MESSAGE!", - effectId: MESSAGE_EFFECTS.loud, - }); - console.log(`loud: ${loudMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Slam effect - const slamMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "BAM!", - effectId: MESSAGE_EFFECTS.slam, - }); - console.log(`slam: ${slamMessage.guid}`); - - await new Promise((resolve) => setTimeout(resolve, 5000)); - - // Invisible ink effect - const invisibleMessage = await sdk.messages.sendMessage({ - chatGuid: CHAT_GUID, - message: "Secret message!", - effectId: MESSAGE_EFFECTS.invisible_ink, - }); - console.log(`invisible ink: ${invisibleMessage.guid}`); - - console.log("\nAvailable effects:"); - console.log(JSON.stringify(MESSAGE_EFFECTS, null, 2)); + for (const effect of BUBBLE_EFFECTS) { + const msg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: EFFECT_MESSAGES[effect], + bubbleEffect: effect, + }); + console.log(`${effect}: ${msg.guid}`); + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + + console.log("\nAvailable bubble effects:"); + for (const effect of BUBBLE_EFFECTS) { + console.log(` ${effect}`); + } } catch (error) { handleError(error, "Failed to send message with effect"); - console.log("\nNote: Effects require Private API to be enabled"); + console.log("\nNote: Bubble effects require Private API to be enabled"); } await sdk.close(); diff --git a/examples/message-styled.ts b/examples/message-styled.ts new file mode 100644 index 0000000..9b37bf6 --- /dev/null +++ b/examples/message-styled.ts @@ -0,0 +1,133 @@ +import { createSDK, handleError } from "./utils"; + +const CHAT_GUID = process.env.CHAT_GUID || "any;-;+1234567890"; + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +async function main() { + const sdk = createSDK(); + + sdk.on("ready", async () => { + console.log("=== Text Styles & Animations (requires Private API) ===\n"); + + try { + const boldMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "This is bold text", + textStyles: [{ start: 8, end: 12, bold: true }], + }); + console.log(`bold: ${boldMsg.guid}`); + await sleep(2000); + + const italicMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "This is italic text", + textStyles: [{ start: 8, end: 14, italic: true }], + }); + console.log(`italic: ${italicMsg.guid}`); + await sleep(2000); + + const underlineMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "This is underlined", + textStyles: [{ start: 8, end: 18, underline: true }], + }); + console.log(`underline: ${underlineMsg.guid}`); + await sleep(2000); + + const strikeMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "This is struck through", + textStyles: [{ start: 8, end: 22, strikethrough: true }], + }); + console.log(`strikethrough: ${strikeMsg.guid}`); + await sleep(2000); + + const multiStyleMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "Bold here, italic there, underline everywhere", + textStyles: [ + { start: 0, end: 9, bold: true }, + { start: 11, end: 23, italic: true }, + { start: 25, end: 45, underline: true }, + ], + }); + console.log(`multi-range: ${multiStyleMsg.guid}`); + await sleep(2000); + + const rippleMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "Ripple wave!", + textAnimation: "ripple", + }); + console.log(`ripple: ${rippleMsg.guid}`); + await sleep(2000); + + const explodeMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "Boom! Explode!", + textAnimation: "explode", + }); + console.log(`explode: ${explodeMsg.guid}`); + await sleep(2000); + + const bloomMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "Bloom!", + textAnimation: "bloom", + }); + console.log(`bloom: ${bloomMsg.guid}`); + await sleep(2000); + + const styledAnimMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "Bold text with jitter!", + textStyles: [{ start: 0, end: 9, bold: true }], + textAnimation: "jitter", + }); + console.log(`bold + jitter: ${styledAnimMsg.guid}`); + await sleep(2000); + + const confettiMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "Confetti!", + bubbleEffect: "confetti", + }); + console.log(`confetti: ${confettiMsg.guid}`); + await sleep(2000); + + const slamMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "BAM!", + bubbleEffect: "slam", + }); + console.log(`slam: ${slamMsg.guid}`); + await sleep(2000); + + const allMsg = await sdk.messages.sendMessage({ + chatGuid: CHAT_GUID, + message: "Bold shaking fireworks!", + textStyles: [{ start: 0, end: 4, bold: true }], + textAnimation: "shake", + bubbleEffect: "fireworks", + }); + console.log(`all layers: ${allMsg.guid}`); + + console.log("\n--- Three independent layers ---"); + console.log(" textStyles: per-range formatting (bold / italic / underline / strikethrough)"); + console.log(" textAnimation: whole-message character animation (big / shake / ripple / bloom / …)"); + console.log(" bubbleEffect: bubble / screen effect (confetti / lasers / slam / …)"); + } catch (error) { + handleError(error, "Failed to send styled message"); + } + + await sdk.close(); + process.exit(0); + }); + + await sdk.connect(); +} + +main().catch(console.error); diff --git a/lib/auto-create-chat.ts b/lib/auto-create-chat.ts index b6043c0..7b4aca3 100644 --- a/lib/auto-create-chat.ts +++ b/lib/auto-create-chat.ts @@ -1,4 +1,5 @@ import type { AxiosInstance } from "axios"; +import type { BubbleEffect, TextAnimation, TextStyle } from "../types"; /** * Checks if an error indicates that a chat does not exist. @@ -52,17 +53,21 @@ export async function createChatWithMessage(options: { message: string; tempGuid?: string; subject?: string; - effectId?: string; + bubbleEffect?: BubbleEffect; + textStyles?: TextStyle[]; + textAnimation?: TextAnimation; service?: "iMessage" | "SMS"; }): Promise { - const { http, address, message, tempGuid, subject, effectId, service } = options; + const { http, address, message, tempGuid, subject, bubbleEffect, textStyles, textAnimation, service } = options; try { const response = await http.post("/api/v1/chat/new", { addresses: [address], message, tempGuid, subject, - effectId, + bubbleEffect, + textStyles, + textAnimation, ...(service && { service }), }); return response.data.data?.guid; diff --git a/modules/chat.ts b/modules/chat.ts index cb963ea..27d2f63 100644 --- a/modules/chat.ts +++ b/modules/chat.ts @@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises"; import path from "node:path"; import type { AxiosInstance } from "axios"; import FormData from "form-data"; -import type { ChatResponse, MessageResponse } from "../types"; +import type { BubbleEffect, ChatResponse, MessageResponse } from "../types"; export class ChatModule { constructor(private readonly http: AxiosInstance) {} @@ -25,7 +25,7 @@ export class ChatModule { service?: "iMessage" | "SMS"; tempGuid?: string; subject?: string; - effectId?: string; + bubbleEffect?: BubbleEffect; attributedBody?: Record; }): Promise { const response = await this.http.post("/api/v1/chat/new", options); diff --git a/modules/message.ts b/modules/message.ts index 15c9dea..3a9a711 100644 --- a/modules/message.ts +++ b/modules/message.ts @@ -31,7 +31,9 @@ export class MessageModule { message: options.message, tempGuid, subject: options.subject, - effectId: options.effectId, + bubbleEffect: options.bubbleEffect, + textStyles: options.textStyles, + textAnimation: options.textAnimation, service, }); return { guid: tempGuid, text: options.message, dateCreated: Date.now() } as MessageResponse; diff --git a/types/message.ts b/types/message.ts index 71430da..1f6560e 100644 --- a/types/message.ts +++ b/types/message.ts @@ -2,15 +2,63 @@ import type { Attachment, AttachmentResponse } from "./attachment"; import type { Chat, ChatResponse } from "./chat"; import type { Handle, HandleResponse } from "./handle"; +export interface TextStyle { + start: number; + end: number; + bold?: boolean; + italic?: boolean; + underline?: boolean; + strikethrough?: boolean; +} + +export type TextAnimation = "big" | "small" | "shake" | "nod" | "explode" | "ripple" | "bloom" | "jitter"; + +export type BubbleEffect = + | "slam" + | "loud" + | "gentle" + | "invisibleInk" + | "confetti" + | "lasers" + | "fireworks" + | "balloons" + | "hearts" + | "shootingStar" + | "celebration" + | "echo" + | "spotlight"; + export interface SendMessageOptions { chatGuid: string; message: string; tempGuid?: string; subject?: string; - effectId?: string; selectedMessageGuid?: string; partIndex?: number; richLink?: boolean; + textStyles?: TextStyle[]; + textAnimation?: TextAnimation; + bubbleEffect?: BubbleEffect; +} + +export interface SendIMessageAppOptions { + chatGuid: string; + balloonBundleId: string; + appName: string; + url: string; + ldtext?: string; + layoutClass?: string; + userInfo?: { + caption?: string; + subcaption?: string; + "secondary-subcaption"?: string; + "tertiary-subcaption"?: string; + "image-title"?: string; + "image-subtitle"?: string; + }; + appId?: number; + imageBase64?: string; + sessionIdentifier?: string; } export interface MessageData {