@photon-ai/advanced-imessage-kit 1.6.1 → 1.8.0

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/README.md CHANGED
@@ -34,6 +34,7 @@ Advanced iMessage Kit is a full-featured iMessage SDK for **reading**, **sending
34
34
  | [Reply Stickers](#send-stickers) | Attach sticker to a message bubble | `attachments.sendSticker()` | [message-reply-sticker.ts](./examples/message-reply-sticker.ts) |
35
35
  | [Download Attachments](#download-attachments) | Download received files and media | `attachments.downloadAttachment()` | [attachment-download.ts](./examples/attachment-download.ts) |
36
36
  | [Get Chats](#get-chats) | List all conversations | `chats.getChats()` | [chat-fetch.ts](./examples/chat-fetch.ts) |
37
+ | [Get Chat Participants](#get-chat-participants) | View group chat participants | `chats.getChat()` | [chat-participants.ts](./examples/chat-participants.ts) |
37
38
  | [Manage Group Chats](#manage-group-chats) | Add/remove members, rename groups | `chats.addParticipant()` | [chat-group.ts](./examples/chat-group.ts) |
38
39
  | [Typing Indicators](#typing-indicators) | Show "typing..." status | `chats.startTyping()` | [message-typing.ts](./examples/message-typing.ts) |
39
40
  | [Get Contacts](#get-contacts) | Fetch device contacts | `contacts.getContacts()` | [contact-list.ts](./examples/contact-list.ts) |
@@ -291,6 +292,37 @@ const chat = await sdk.chats.getChat("chat-guid", {
291
292
  });
292
293
  ```
293
294
 
295
+ ### Get Chat Participants
296
+
297
+ Get participants from group chats and display them with contact names:
298
+
299
+ ```typescript
300
+ const chats = await sdk.chats.getChats();
301
+ const groups = chats.filter((chat) => chat.style === 43); // Filter group chats
302
+
303
+ // Get contacts for name mapping
304
+ const contacts = await sdk.contacts.getContacts();
305
+ const nameMap = new Map<string, string>();
306
+ for (const c of contacts) {
307
+ const name = c.displayName || c.firstName || "";
308
+ if (!name) continue;
309
+ for (const p of c.phoneNumbers || []) nameMap.set(p.address, name);
310
+ for (const e of c.emails || []) nameMap.set(e.address, name);
311
+ }
312
+
313
+ // Display participants
314
+ groups.forEach((group) => {
315
+ console.log(`Group: ${group.displayName || group.chatIdentifier}`);
316
+ group.participants?.forEach((p) => {
317
+ const name = nameMap.get(p.address);
318
+ const display = name ? `${name} <${p.address}>` : p.address;
319
+ console.log(` - ${display} (${p.service})`);
320
+ });
321
+ });
322
+ ```
323
+
324
+ > Example: [chat-participants.ts](./examples/chat-participants.ts)
325
+
294
326
  ### Create Chat
295
327
 
296
328
  ```typescript
@@ -926,11 +958,12 @@ bun run examples/<filename>.ts
926
958
 
927
959
  ### Chats & Groups
928
960
 
929
- | File | Description |
930
- | ------------------------------------------------- | ----------------- |
931
- | [chat-fetch.ts](./examples/chat-fetch.ts) | Get chat list |
932
- | [chat-group.ts](./examples/chat-group.ts) | Manage groups |
933
- | [message-typing.ts](./examples/message-typing.ts) | Typing indicators |
961
+ | File | Description |
962
+ | ------------------------------------------------------- | ---------------------- |
963
+ | [chat-fetch.ts](./examples/chat-fetch.ts) | Get chat list |
964
+ | [chat-participants.ts](./examples/chat-participants.ts) | Get group participants |
965
+ | [chat-group.ts](./examples/chat-group.ts) | Manage groups |
966
+ | [message-typing.ts](./examples/message-typing.ts) | Typing indicators |
934
967
 
935
968
  ### Contacts & Services
936
969
 
package/dist/index.cjs CHANGED
@@ -134,6 +134,48 @@ var getLogger = (tag) => {
134
134
  }
135
135
  return logger;
136
136
  };
137
+
138
+ // lib/auto-create-chat.ts
139
+ function isChatNotExistError(error) {
140
+ const axiosError = error;
141
+ const errorMsg = axiosError?.response?.data?.error?.message || axiosError?.response?.data?.message || "";
142
+ const lowerMsg = errorMsg.toLowerCase();
143
+ return lowerMsg.includes("chat does not exist") || lowerMsg.includes("chat not found");
144
+ }
145
+ function extractAddress(chatGuid) {
146
+ const parts = chatGuid.split(";-;");
147
+ if (parts.length !== 2 || !parts[1]) {
148
+ return void 0;
149
+ }
150
+ return parts[1];
151
+ }
152
+ function extractService(chatGuid) {
153
+ if (!chatGuid) return void 0;
154
+ const prefix = chatGuid.split(";")[0]?.toLowerCase();
155
+ if (prefix === "imessage") return "iMessage";
156
+ if (prefix === "sms") return "SMS";
157
+ return void 0;
158
+ }
159
+ async function createChatWithMessage(options) {
160
+ const { http, address, message, tempGuid, subject, effectId, service } = options;
161
+ try {
162
+ const response = await http.post("/api/v1/chat/new", {
163
+ addresses: [address],
164
+ message,
165
+ tempGuid,
166
+ subject,
167
+ effectId,
168
+ ...service && { service }
169
+ });
170
+ return response.data.data?.guid;
171
+ } catch (error) {
172
+ throw new Error(
173
+ `Failed to create chat with address "${address}": ${error instanceof Error ? error.message : String(error)}`
174
+ );
175
+ }
176
+ }
177
+
178
+ // modules/attachment.ts
137
179
  var AttachmentModule = class {
138
180
  constructor(http, enqueueSend = (task) => task()) {
139
181
  this.http = http;
@@ -176,52 +218,94 @@ var AttachmentModule = class {
176
218
  });
177
219
  return response.data.data.blurhash;
178
220
  }
221
+ /**
222
+ * Ensures the chat exists by creating it if it doesn't already exist.
223
+ */
224
+ async ensureChatExists(chatGuid) {
225
+ const address = extractAddress(chatGuid);
226
+ if (!address) return;
227
+ const service = extractService(chatGuid);
228
+ await this.http.post("/api/v1/chat/new", {
229
+ addresses: [address],
230
+ ...service && { service }
231
+ });
232
+ }
179
233
  async sendAttachment(options) {
180
234
  return this.enqueueSend(async () => {
181
235
  const fileBuffer = await promises.readFile(options.filePath);
182
236
  const fileName = options.fileName || path__namespace.default.basename(options.filePath);
183
- const form = new FormData__default.default();
184
- form.append("chatGuid", options.chatGuid);
185
- form.append("attachment", fileBuffer, fileName);
186
- form.append("name", fileName);
187
- form.append("tempGuid", crypto.randomUUID());
188
- if (options.isAudioMessage !== void 0) {
189
- form.append("isAudioMessage", options.isAudioMessage.toString());
190
- if (options.isAudioMessage) {
191
- form.append("method", "private-api");
237
+ const tempGuid = crypto.randomUUID();
238
+ const buildForm = () => {
239
+ const form = new FormData__default.default();
240
+ form.append("chatGuid", options.chatGuid);
241
+ form.append("attachment", fileBuffer, fileName);
242
+ form.append("name", fileName);
243
+ form.append("tempGuid", tempGuid);
244
+ if (options.isAudioMessage !== void 0) {
245
+ form.append("isAudioMessage", options.isAudioMessage.toString());
246
+ if (options.isAudioMessage) {
247
+ form.append("method", "private-api");
248
+ }
192
249
  }
250
+ if (options.selectedMessageGuid) {
251
+ form.append("selectedMessageGuid", options.selectedMessageGuid);
252
+ }
253
+ return form;
254
+ };
255
+ try {
256
+ const form = buildForm();
257
+ const response = await this.http.post("/api/v1/message/attachment", form, {
258
+ headers: form.getHeaders()
259
+ });
260
+ return response.data.data;
261
+ } catch (error) {
262
+ if (!isChatNotExistError(error)) throw error;
263
+ await this.ensureChatExists(options.chatGuid);
264
+ const form = buildForm();
265
+ const response = await this.http.post("/api/v1/message/attachment", form, {
266
+ headers: form.getHeaders()
267
+ });
268
+ return response.data.data;
193
269
  }
194
- if (options.selectedMessageGuid) {
195
- form.append("selectedMessageGuid", options.selectedMessageGuid);
196
- }
197
- const response = await this.http.post("/api/v1/message/attachment", form, {
198
- headers: form.getHeaders()
199
- });
200
- return response.data.data;
201
270
  });
202
271
  }
203
272
  async sendSticker(options) {
204
273
  return this.enqueueSend(async () => {
205
274
  const fileName = options.fileName || path__namespace.default.basename(options.filePath);
206
- const form = new FormData__default.default();
207
- form.append("attachment", await promises.readFile(options.filePath), fileName);
208
- form.append("name", fileName);
209
- form.append("chatGuid", options.chatGuid);
210
- form.append("isSticker", "true");
211
- form.append("method", "private-api");
212
- if (options.selectedMessageGuid) {
213
- form.append("selectedMessageGuid", options.selectedMessageGuid);
214
- form.append("partIndex", "0");
215
- form.append("stickerX", String(options.stickerX ?? 0.5));
216
- form.append("stickerY", String(options.stickerY ?? 0.5));
217
- form.append("stickerScale", String(options.stickerScale ?? 0.75));
218
- form.append("stickerRotation", String(options.stickerRotation ?? 0));
219
- form.append("stickerWidth", String(options.stickerWidth ?? 300));
275
+ const fileBuffer = await promises.readFile(options.filePath);
276
+ const buildForm = () => {
277
+ const form = new FormData__default.default();
278
+ form.append("attachment", fileBuffer, fileName);
279
+ form.append("name", fileName);
280
+ form.append("chatGuid", options.chatGuid);
281
+ form.append("isSticker", "true");
282
+ form.append("method", "private-api");
283
+ if (options.selectedMessageGuid) {
284
+ form.append("selectedMessageGuid", options.selectedMessageGuid);
285
+ form.append("partIndex", "0");
286
+ form.append("stickerX", String(options.stickerX ?? 0.5));
287
+ form.append("stickerY", String(options.stickerY ?? 0.5));
288
+ form.append("stickerScale", String(options.stickerScale ?? 0.75));
289
+ form.append("stickerRotation", String(options.stickerRotation ?? 0));
290
+ form.append("stickerWidth", String(options.stickerWidth ?? 300));
291
+ }
292
+ return form;
293
+ };
294
+ try {
295
+ const form = buildForm();
296
+ const { data } = await this.http.post("/api/v1/message/attachment", form, {
297
+ headers: form.getHeaders()
298
+ });
299
+ return data.data;
300
+ } catch (error) {
301
+ if (!isChatNotExistError(error)) throw error;
302
+ await this.ensureChatExists(options.chatGuid);
303
+ const form = buildForm();
304
+ const { data } = await this.http.post("/api/v1/message/attachment", form, {
305
+ headers: form.getHeaders()
306
+ });
307
+ return data.data;
220
308
  }
221
- const { data } = await this.http.post("/api/v1/message/attachment", form, {
222
- headers: form.getHeaders()
223
- });
224
- return data.data;
225
309
  });
226
310
  }
227
311
  };
@@ -266,8 +350,8 @@ var ChatModule = class {
266
350
  return response.data.data;
267
351
  }
268
352
  async removeParticipant(chatGuid, address) {
269
- const response = await this.http.delete(`/api/v1/chat/${encodeURIComponent(chatGuid)}/participant`, {
270
- data: { address }
353
+ const response = await this.http.post(`/api/v1/chat/${encodeURIComponent(chatGuid)}/participant/remove`, {
354
+ address
271
355
  });
272
356
  return response.data.data;
273
357
  }
@@ -421,8 +505,25 @@ var MessageModule = class {
421
505
  return this.enqueueSend(async () => {
422
506
  const tempGuid = options.tempGuid || crypto.randomUUID();
423
507
  const payload = { ...options, tempGuid };
424
- const response = await this.http.post("/api/v1/message/text", payload);
425
- return response.data.data;
508
+ try {
509
+ const response = await this.http.post("/api/v1/message/text", payload);
510
+ return response.data.data;
511
+ } catch (error) {
512
+ if (!isChatNotExistError(error)) throw error;
513
+ const address = extractAddress(options.chatGuid);
514
+ if (!address) throw error;
515
+ const service = extractService(options.chatGuid);
516
+ await createChatWithMessage({
517
+ http: this.http,
518
+ address,
519
+ message: options.message,
520
+ tempGuid,
521
+ subject: options.subject,
522
+ effectId: options.effectId,
523
+ service
524
+ });
525
+ return { guid: tempGuid, text: options.message, dateCreated: Date.now() };
526
+ }
426
527
  });
427
528
  }
428
529
  async getMessage(guid, options) {