whatsapp-cloud 0.1.3 → 0.1.5
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/CHANGELOG.md +12 -0
- package/README.md +2 -1
- package/dist/index.cjs +71 -27
- package/dist/index.d.cts +54 -23
- package/dist/index.d.ts +54 -23
- package/dist/index.js +71 -27
- package/package.json +2 -2
- package/src/client/WhatsAppClient.ts +3 -0
- package/src/examples/media-download.ts +65 -0
- package/src/services/index.ts +1 -0
- package/src/services/media/MediaService.ts +71 -0
- package/src/services/media/index.ts +1 -0
- package/src/services/messages/MessagesService.ts +23 -0
- package/src/services/webhooks/WebhooksService.ts +0 -31
- package/src/services/webhooks/utils/extract-messages.ts +1 -1
- package/src/types/index.ts +1 -0
- package/src/types/media/index.ts +12 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ export async function POST(request: NextRequest) {
|
|
|
81
81
|
console.log(`Image from ${context.contact?.name || message.from}`);
|
|
82
82
|
|
|
83
83
|
// Download the image
|
|
84
|
-
const imageData = await client.
|
|
84
|
+
const imageData = await client.media.download(message.image.id);
|
|
85
85
|
|
|
86
86
|
// Process the image (save to storage, analyze, etc.)
|
|
87
87
|
// const buffer = Buffer.from(imageData);
|
|
@@ -107,4 +107,5 @@ export async function POST(request: NextRequest) {
|
|
|
107
107
|
- `client.accounts` - Manage WhatsApp Business Accounts
|
|
108
108
|
- `client.business` - Manage Business Portfolios
|
|
109
109
|
- `client.templates` - Create, retrieve and send WhatsApp templates
|
|
110
|
+
- `client.media` - Upload, download, and manage media files
|
|
110
111
|
- ... more to come very soon. 🕒
|
package/dist/index.cjs
CHANGED
|
@@ -503,6 +503,24 @@ var MessagesService = class {
|
|
|
503
503
|
const client = this.getClient(phoneNumberId);
|
|
504
504
|
return sendReaction(client, input);
|
|
505
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Send any message type using the discriminated union
|
|
508
|
+
*
|
|
509
|
+
* @param message - Any outgoing message (text, image, location, reaction)
|
|
510
|
+
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
511
|
+
*/
|
|
512
|
+
async sendMessage(message, phoneNumberId) {
|
|
513
|
+
switch (message.type) {
|
|
514
|
+
case "text":
|
|
515
|
+
return this.sendText(message, phoneNumberId);
|
|
516
|
+
case "image":
|
|
517
|
+
return this.sendImage(message, phoneNumberId);
|
|
518
|
+
case "location":
|
|
519
|
+
return this.sendLocation(message, phoneNumberId);
|
|
520
|
+
case "reaction":
|
|
521
|
+
return this.sendReaction(message, phoneNumberId);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
506
524
|
};
|
|
507
525
|
|
|
508
526
|
// src/services/accounts/AccountsClient.ts
|
|
@@ -1401,33 +1419,6 @@ var WebhooksService = class {
|
|
|
1401
1419
|
extractStatuses(payload) {
|
|
1402
1420
|
return extractStatuses(payload);
|
|
1403
1421
|
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Download media file by media ID
|
|
1406
|
-
*
|
|
1407
|
-
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
1408
|
-
* Uses the access token from the client configuration automatically.
|
|
1409
|
-
*
|
|
1410
|
-
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
1411
|
-
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
1412
|
-
* @throws Error if download fails or media ID is invalid
|
|
1413
|
-
*
|
|
1414
|
-
* @example
|
|
1415
|
-
* ```typescript
|
|
1416
|
-
* client.webhooks.handle(req.body, {
|
|
1417
|
-
* image: async (message, context) => {
|
|
1418
|
-
* const mediaData = await client.webhooks.downloadMedia(message.image.id);
|
|
1419
|
-
* // Upload to S3, save to disk, etc.
|
|
1420
|
-
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
1421
|
-
* },
|
|
1422
|
-
* });
|
|
1423
|
-
* ```
|
|
1424
|
-
*/
|
|
1425
|
-
async downloadMedia(mediaId) {
|
|
1426
|
-
if (!mediaId || mediaId.trim().length === 0) {
|
|
1427
|
-
throw new Error("Media ID is required");
|
|
1428
|
-
}
|
|
1429
|
-
return this.httpClient.getBinary(`/${mediaId}`);
|
|
1430
|
-
}
|
|
1431
1422
|
/**
|
|
1432
1423
|
* Validate webhook payload structure
|
|
1433
1424
|
*
|
|
@@ -1543,6 +1534,57 @@ var WebhooksService = class {
|
|
|
1543
1534
|
}
|
|
1544
1535
|
};
|
|
1545
1536
|
|
|
1537
|
+
// src/services/media/MediaService.ts
|
|
1538
|
+
var MediaService = class {
|
|
1539
|
+
constructor(httpClient) {
|
|
1540
|
+
this.httpClient = httpClient;
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Download media file by media ID
|
|
1544
|
+
*
|
|
1545
|
+
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
1546
|
+
* Uses the access token from the client configuration automatically.
|
|
1547
|
+
*
|
|
1548
|
+
* According to WhatsApp API docs, you cannot download directly from the media ID endpoint.
|
|
1549
|
+
* The flow is:
|
|
1550
|
+
* 1. GET /MEDIA_ID → returns JSON metadata with a URL
|
|
1551
|
+
* 2. GET /MEDIA_URL → returns the actual binary data
|
|
1552
|
+
*
|
|
1553
|
+
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
1554
|
+
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
1555
|
+
* @throws Error if download fails or media ID is invalid
|
|
1556
|
+
*
|
|
1557
|
+
* @example
|
|
1558
|
+
* ```typescript
|
|
1559
|
+
* const mediaData = await client.media.download(message.image.id);
|
|
1560
|
+
* // Upload to S3, save to disk, etc.
|
|
1561
|
+
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
1562
|
+
* ```
|
|
1563
|
+
*/
|
|
1564
|
+
async download(mediaId) {
|
|
1565
|
+
if (!mediaId || mediaId.trim().length === 0) {
|
|
1566
|
+
throw new Error("Media ID is required");
|
|
1567
|
+
}
|
|
1568
|
+
const metadata = await this.httpClient.get(`/${mediaId}`);
|
|
1569
|
+
const response = await fetch(metadata.url, {
|
|
1570
|
+
method: "GET",
|
|
1571
|
+
headers: {
|
|
1572
|
+
Authorization: `Bearer ${this.httpClient.accessToken}`
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
if (!response.ok) {
|
|
1576
|
+
let errorMessage = `API Error: ${response.statusText}`;
|
|
1577
|
+
try {
|
|
1578
|
+
const error = await response.json();
|
|
1579
|
+
errorMessage = `API Error: ${error.error?.message || response.statusText} (${error.error?.code || response.status})`;
|
|
1580
|
+
} catch {
|
|
1581
|
+
}
|
|
1582
|
+
throw new Error(errorMessage);
|
|
1583
|
+
}
|
|
1584
|
+
return response.arrayBuffer();
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1546
1588
|
// src/client/WhatsAppClient.ts
|
|
1547
1589
|
var import_zod9 = require("zod");
|
|
1548
1590
|
var WhatsAppClient = class {
|
|
@@ -1551,6 +1593,7 @@ var WhatsAppClient = class {
|
|
|
1551
1593
|
business;
|
|
1552
1594
|
templates;
|
|
1553
1595
|
webhooks;
|
|
1596
|
+
media;
|
|
1554
1597
|
httpClient;
|
|
1555
1598
|
constructor(config) {
|
|
1556
1599
|
let validated;
|
|
@@ -1568,6 +1611,7 @@ var WhatsAppClient = class {
|
|
|
1568
1611
|
this.business = new BusinessService(this.httpClient);
|
|
1569
1612
|
this.templates = new TemplatesService(this.httpClient);
|
|
1570
1613
|
this.webhooks = new WebhooksService(this.httpClient);
|
|
1614
|
+
this.media = new MediaService(this.httpClient);
|
|
1571
1615
|
}
|
|
1572
1616
|
/**
|
|
1573
1617
|
* Debug the current access token
|
package/dist/index.d.cts
CHANGED
|
@@ -279,6 +279,13 @@ declare class MessagesService {
|
|
|
279
279
|
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
280
280
|
*/
|
|
281
281
|
sendReaction(input: SendReactionInput, phoneNumberId?: string): Promise<MessageResponse>;
|
|
282
|
+
/**
|
|
283
|
+
* Send any message type using the discriminated union
|
|
284
|
+
*
|
|
285
|
+
* @param message - Any outgoing message (text, image, location, reaction)
|
|
286
|
+
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
287
|
+
*/
|
|
288
|
+
sendMessage(message: OutgoingMessage, phoneNumberId?: string): Promise<MessageResponse>;
|
|
282
289
|
}
|
|
283
290
|
|
|
284
291
|
/**
|
|
@@ -1377,28 +1384,6 @@ declare class WebhooksService {
|
|
|
1377
1384
|
* @returns Flat array of status updates
|
|
1378
1385
|
*/
|
|
1379
1386
|
extractStatuses(payload: WebhookPayload): Status[];
|
|
1380
|
-
/**
|
|
1381
|
-
* Download media file by media ID
|
|
1382
|
-
*
|
|
1383
|
-
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
1384
|
-
* Uses the access token from the client configuration automatically.
|
|
1385
|
-
*
|
|
1386
|
-
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
1387
|
-
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
1388
|
-
* @throws Error if download fails or media ID is invalid
|
|
1389
|
-
*
|
|
1390
|
-
* @example
|
|
1391
|
-
* ```typescript
|
|
1392
|
-
* client.webhooks.handle(req.body, {
|
|
1393
|
-
* image: async (message, context) => {
|
|
1394
|
-
* const mediaData = await client.webhooks.downloadMedia(message.image.id);
|
|
1395
|
-
* // Upload to S3, save to disk, etc.
|
|
1396
|
-
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
1397
|
-
* },
|
|
1398
|
-
* });
|
|
1399
|
-
* ```
|
|
1400
|
-
*/
|
|
1401
|
-
downloadMedia(mediaId: string): Promise<ArrayBuffer>;
|
|
1402
1387
|
/**
|
|
1403
1388
|
* Validate webhook payload structure
|
|
1404
1389
|
*
|
|
@@ -1429,6 +1414,39 @@ declare class WebhooksService {
|
|
|
1429
1414
|
handle<THandlers extends MessageHandlers<any>>(payload: unknown, handlers: THandlers, options?: HandleOptions): void;
|
|
1430
1415
|
}
|
|
1431
1416
|
|
|
1417
|
+
/**
|
|
1418
|
+
* Media service for downloading WhatsApp media files
|
|
1419
|
+
*
|
|
1420
|
+
* This service handles downloading media files from WhatsApp servers.
|
|
1421
|
+
*/
|
|
1422
|
+
declare class MediaService {
|
|
1423
|
+
private readonly httpClient;
|
|
1424
|
+
constructor(httpClient: HttpClient);
|
|
1425
|
+
/**
|
|
1426
|
+
* Download media file by media ID
|
|
1427
|
+
*
|
|
1428
|
+
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
1429
|
+
* Uses the access token from the client configuration automatically.
|
|
1430
|
+
*
|
|
1431
|
+
* According to WhatsApp API docs, you cannot download directly from the media ID endpoint.
|
|
1432
|
+
* The flow is:
|
|
1433
|
+
* 1. GET /MEDIA_ID → returns JSON metadata with a URL
|
|
1434
|
+
* 2. GET /MEDIA_URL → returns the actual binary data
|
|
1435
|
+
*
|
|
1436
|
+
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
1437
|
+
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
1438
|
+
* @throws Error if download fails or media ID is invalid
|
|
1439
|
+
*
|
|
1440
|
+
* @example
|
|
1441
|
+
* ```typescript
|
|
1442
|
+
* const mediaData = await client.media.download(message.image.id);
|
|
1443
|
+
* // Upload to S3, save to disk, etc.
|
|
1444
|
+
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
1445
|
+
* ```
|
|
1446
|
+
*/
|
|
1447
|
+
download(mediaId: string): Promise<ArrayBuffer>;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1432
1450
|
/**
|
|
1433
1451
|
* Schema for debug token response
|
|
1434
1452
|
* Matches Graph API debug_token endpoint response structure
|
|
@@ -1465,6 +1483,7 @@ declare class WhatsAppClient {
|
|
|
1465
1483
|
readonly business: BusinessService;
|
|
1466
1484
|
readonly templates: TemplatesService;
|
|
1467
1485
|
readonly webhooks: WebhooksService;
|
|
1486
|
+
readonly media: MediaService;
|
|
1468
1487
|
private readonly httpClient;
|
|
1469
1488
|
constructor(config: ClientConfig);
|
|
1470
1489
|
/**
|
|
@@ -1797,6 +1816,18 @@ type TemplateComponent = z.infer<typeof templateComponentSchema>;
|
|
|
1797
1816
|
*/
|
|
1798
1817
|
type TemplateLanguage = z.infer<typeof templateLanguageSchema>;
|
|
1799
1818
|
|
|
1819
|
+
/**
|
|
1820
|
+
* Media metadata response (from GET /MEDIA_ID)
|
|
1821
|
+
*/
|
|
1822
|
+
type MediaMetadata = {
|
|
1823
|
+
messaging_product: "whatsapp";
|
|
1824
|
+
url: string;
|
|
1825
|
+
mime_type: string;
|
|
1826
|
+
sha256: string;
|
|
1827
|
+
file_size: string;
|
|
1828
|
+
id: string;
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1800
1831
|
/**
|
|
1801
1832
|
* Base error class for WhatsApp API errors
|
|
1802
1833
|
*/
|
|
@@ -1836,4 +1867,4 @@ declare class WhatsAppRateLimitError extends WhatsAppAPIError {
|
|
|
1836
1867
|
constructor(message: string, retryAfter?: number | undefined);
|
|
1837
1868
|
}
|
|
1838
1869
|
|
|
1839
|
-
export { type BusinessAccountsListResponse, type ClientConfig, type DebugTokenResponse, type HandleOptions, type IncomingAudioMessage, type IncomingImageMessage, type IncomingMessage, type IncomingTextMessage, type MessageContext, type MessageHandlers, type MessageResponse, type OutgoingImageMessage, type OutgoingLocationMessage, type OutgoingMessage, type OutgoingReactionMessage, type OutgoingTextMessage, type PhoneNumberListResponse, type SendImageInput, type SendLocationInput, type SendReactionInput, type SendTextInput, type Status, type Template, type TemplateBodyComponent, type TemplateButton, type TemplateButtonsComponent, type TemplateComponent, type TemplateCopyCodeButton, type TemplateCreate, type TemplateCreateResponse, type TemplateDelete, type TemplateDeleteResponse, type TemplateFlowButton, type TemplateFooterComponent, type TemplateHeaderComponent, type TemplateLanguage, type TemplateList, type TemplateListResponse, type TemplatePhoneNumberButton, type TemplateQuickReplyButton, type TemplateUpdate, type TemplateUpdateResponse, type TemplateUrlButton, type WebhookContext, type WebhookPayload, WhatsAppAPIError, WhatsAppClient, WhatsAppError, WhatsAppRateLimitError, WhatsAppValidationError, businessAccountResponseSchema, businessAccountsListResponseSchema, clientConfigSchema, debugTokenResponseSchema, incomingAudioMessageSchema, incomingImageMessageSchema, incomingMessageSchema, incomingTextMessageSchema, messageResponseSchema, outgoingImageMessageSchema, outgoingLocationMessageSchema, outgoingMessageSchema, outgoingReactionMessageSchema, outgoingTextMessageSchema, phoneNumberListResponseSchema, phoneNumberResponseSchema, sendImageInputSchema, sendLocationInputSchema, sendReactionInputSchema, sendTextInputSchema, statusSchema, templateBodyComponentSchema, templateButtonSchema, templateButtonsComponentSchema, templateComponentSchema, templateCopyCodeButtonSchema, templateCreateResponseSchema, templateCreateSchema, templateDeleteResponseSchema, templateDeleteSchema, templateFlowButtonSchema, templateFooterComponentSchema, templateHeaderComponentSchema, templateLanguageSchema, templateListResponseSchema, templateListSchema, templatePhoneNumberButtonSchema, templateQuickReplyButtonSchema, templateSchema, templateUpdateResponseSchema, templateUpdateSchema, templateUrlButtonSchema, webhookPayloadSchema };
|
|
1870
|
+
export { type BusinessAccountsListResponse, type ClientConfig, type DebugTokenResponse, type HandleOptions, type IncomingAudioMessage, type IncomingImageMessage, type IncomingMessage, type IncomingTextMessage, type MediaMetadata, type MessageContext, type MessageHandlers, type MessageResponse, type OutgoingImageMessage, type OutgoingLocationMessage, type OutgoingMessage, type OutgoingReactionMessage, type OutgoingTextMessage, type PhoneNumberListResponse, type SendImageInput, type SendLocationInput, type SendReactionInput, type SendTextInput, type Status, type Template, type TemplateBodyComponent, type TemplateButton, type TemplateButtonsComponent, type TemplateComponent, type TemplateCopyCodeButton, type TemplateCreate, type TemplateCreateResponse, type TemplateDelete, type TemplateDeleteResponse, type TemplateFlowButton, type TemplateFooterComponent, type TemplateHeaderComponent, type TemplateLanguage, type TemplateList, type TemplateListResponse, type TemplatePhoneNumberButton, type TemplateQuickReplyButton, type TemplateUpdate, type TemplateUpdateResponse, type TemplateUrlButton, type WebhookContext, type WebhookPayload, WhatsAppAPIError, WhatsAppClient, WhatsAppError, WhatsAppRateLimitError, WhatsAppValidationError, businessAccountResponseSchema, businessAccountsListResponseSchema, clientConfigSchema, debugTokenResponseSchema, incomingAudioMessageSchema, incomingImageMessageSchema, incomingMessageSchema, incomingTextMessageSchema, messageResponseSchema, outgoingImageMessageSchema, outgoingLocationMessageSchema, outgoingMessageSchema, outgoingReactionMessageSchema, outgoingTextMessageSchema, phoneNumberListResponseSchema, phoneNumberResponseSchema, sendImageInputSchema, sendLocationInputSchema, sendReactionInputSchema, sendTextInputSchema, statusSchema, templateBodyComponentSchema, templateButtonSchema, templateButtonsComponentSchema, templateComponentSchema, templateCopyCodeButtonSchema, templateCreateResponseSchema, templateCreateSchema, templateDeleteResponseSchema, templateDeleteSchema, templateFlowButtonSchema, templateFooterComponentSchema, templateHeaderComponentSchema, templateLanguageSchema, templateListResponseSchema, templateListSchema, templatePhoneNumberButtonSchema, templateQuickReplyButtonSchema, templateSchema, templateUpdateResponseSchema, templateUpdateSchema, templateUrlButtonSchema, webhookPayloadSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -279,6 +279,13 @@ declare class MessagesService {
|
|
|
279
279
|
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
280
280
|
*/
|
|
281
281
|
sendReaction(input: SendReactionInput, phoneNumberId?: string): Promise<MessageResponse>;
|
|
282
|
+
/**
|
|
283
|
+
* Send any message type using the discriminated union
|
|
284
|
+
*
|
|
285
|
+
* @param message - Any outgoing message (text, image, location, reaction)
|
|
286
|
+
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
287
|
+
*/
|
|
288
|
+
sendMessage(message: OutgoingMessage, phoneNumberId?: string): Promise<MessageResponse>;
|
|
282
289
|
}
|
|
283
290
|
|
|
284
291
|
/**
|
|
@@ -1377,28 +1384,6 @@ declare class WebhooksService {
|
|
|
1377
1384
|
* @returns Flat array of status updates
|
|
1378
1385
|
*/
|
|
1379
1386
|
extractStatuses(payload: WebhookPayload): Status[];
|
|
1380
|
-
/**
|
|
1381
|
-
* Download media file by media ID
|
|
1382
|
-
*
|
|
1383
|
-
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
1384
|
-
* Uses the access token from the client configuration automatically.
|
|
1385
|
-
*
|
|
1386
|
-
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
1387
|
-
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
1388
|
-
* @throws Error if download fails or media ID is invalid
|
|
1389
|
-
*
|
|
1390
|
-
* @example
|
|
1391
|
-
* ```typescript
|
|
1392
|
-
* client.webhooks.handle(req.body, {
|
|
1393
|
-
* image: async (message, context) => {
|
|
1394
|
-
* const mediaData = await client.webhooks.downloadMedia(message.image.id);
|
|
1395
|
-
* // Upload to S3, save to disk, etc.
|
|
1396
|
-
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
1397
|
-
* },
|
|
1398
|
-
* });
|
|
1399
|
-
* ```
|
|
1400
|
-
*/
|
|
1401
|
-
downloadMedia(mediaId: string): Promise<ArrayBuffer>;
|
|
1402
1387
|
/**
|
|
1403
1388
|
* Validate webhook payload structure
|
|
1404
1389
|
*
|
|
@@ -1429,6 +1414,39 @@ declare class WebhooksService {
|
|
|
1429
1414
|
handle<THandlers extends MessageHandlers<any>>(payload: unknown, handlers: THandlers, options?: HandleOptions): void;
|
|
1430
1415
|
}
|
|
1431
1416
|
|
|
1417
|
+
/**
|
|
1418
|
+
* Media service for downloading WhatsApp media files
|
|
1419
|
+
*
|
|
1420
|
+
* This service handles downloading media files from WhatsApp servers.
|
|
1421
|
+
*/
|
|
1422
|
+
declare class MediaService {
|
|
1423
|
+
private readonly httpClient;
|
|
1424
|
+
constructor(httpClient: HttpClient);
|
|
1425
|
+
/**
|
|
1426
|
+
* Download media file by media ID
|
|
1427
|
+
*
|
|
1428
|
+
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
1429
|
+
* Uses the access token from the client configuration automatically.
|
|
1430
|
+
*
|
|
1431
|
+
* According to WhatsApp API docs, you cannot download directly from the media ID endpoint.
|
|
1432
|
+
* The flow is:
|
|
1433
|
+
* 1. GET /MEDIA_ID → returns JSON metadata with a URL
|
|
1434
|
+
* 2. GET /MEDIA_URL → returns the actual binary data
|
|
1435
|
+
*
|
|
1436
|
+
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
1437
|
+
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
1438
|
+
* @throws Error if download fails or media ID is invalid
|
|
1439
|
+
*
|
|
1440
|
+
* @example
|
|
1441
|
+
* ```typescript
|
|
1442
|
+
* const mediaData = await client.media.download(message.image.id);
|
|
1443
|
+
* // Upload to S3, save to disk, etc.
|
|
1444
|
+
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
1445
|
+
* ```
|
|
1446
|
+
*/
|
|
1447
|
+
download(mediaId: string): Promise<ArrayBuffer>;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1432
1450
|
/**
|
|
1433
1451
|
* Schema for debug token response
|
|
1434
1452
|
* Matches Graph API debug_token endpoint response structure
|
|
@@ -1465,6 +1483,7 @@ declare class WhatsAppClient {
|
|
|
1465
1483
|
readonly business: BusinessService;
|
|
1466
1484
|
readonly templates: TemplatesService;
|
|
1467
1485
|
readonly webhooks: WebhooksService;
|
|
1486
|
+
readonly media: MediaService;
|
|
1468
1487
|
private readonly httpClient;
|
|
1469
1488
|
constructor(config: ClientConfig);
|
|
1470
1489
|
/**
|
|
@@ -1797,6 +1816,18 @@ type TemplateComponent = z.infer<typeof templateComponentSchema>;
|
|
|
1797
1816
|
*/
|
|
1798
1817
|
type TemplateLanguage = z.infer<typeof templateLanguageSchema>;
|
|
1799
1818
|
|
|
1819
|
+
/**
|
|
1820
|
+
* Media metadata response (from GET /MEDIA_ID)
|
|
1821
|
+
*/
|
|
1822
|
+
type MediaMetadata = {
|
|
1823
|
+
messaging_product: "whatsapp";
|
|
1824
|
+
url: string;
|
|
1825
|
+
mime_type: string;
|
|
1826
|
+
sha256: string;
|
|
1827
|
+
file_size: string;
|
|
1828
|
+
id: string;
|
|
1829
|
+
};
|
|
1830
|
+
|
|
1800
1831
|
/**
|
|
1801
1832
|
* Base error class for WhatsApp API errors
|
|
1802
1833
|
*/
|
|
@@ -1836,4 +1867,4 @@ declare class WhatsAppRateLimitError extends WhatsAppAPIError {
|
|
|
1836
1867
|
constructor(message: string, retryAfter?: number | undefined);
|
|
1837
1868
|
}
|
|
1838
1869
|
|
|
1839
|
-
export { type BusinessAccountsListResponse, type ClientConfig, type DebugTokenResponse, type HandleOptions, type IncomingAudioMessage, type IncomingImageMessage, type IncomingMessage, type IncomingTextMessage, type MessageContext, type MessageHandlers, type MessageResponse, type OutgoingImageMessage, type OutgoingLocationMessage, type OutgoingMessage, type OutgoingReactionMessage, type OutgoingTextMessage, type PhoneNumberListResponse, type SendImageInput, type SendLocationInput, type SendReactionInput, type SendTextInput, type Status, type Template, type TemplateBodyComponent, type TemplateButton, type TemplateButtonsComponent, type TemplateComponent, type TemplateCopyCodeButton, type TemplateCreate, type TemplateCreateResponse, type TemplateDelete, type TemplateDeleteResponse, type TemplateFlowButton, type TemplateFooterComponent, type TemplateHeaderComponent, type TemplateLanguage, type TemplateList, type TemplateListResponse, type TemplatePhoneNumberButton, type TemplateQuickReplyButton, type TemplateUpdate, type TemplateUpdateResponse, type TemplateUrlButton, type WebhookContext, type WebhookPayload, WhatsAppAPIError, WhatsAppClient, WhatsAppError, WhatsAppRateLimitError, WhatsAppValidationError, businessAccountResponseSchema, businessAccountsListResponseSchema, clientConfigSchema, debugTokenResponseSchema, incomingAudioMessageSchema, incomingImageMessageSchema, incomingMessageSchema, incomingTextMessageSchema, messageResponseSchema, outgoingImageMessageSchema, outgoingLocationMessageSchema, outgoingMessageSchema, outgoingReactionMessageSchema, outgoingTextMessageSchema, phoneNumberListResponseSchema, phoneNumberResponseSchema, sendImageInputSchema, sendLocationInputSchema, sendReactionInputSchema, sendTextInputSchema, statusSchema, templateBodyComponentSchema, templateButtonSchema, templateButtonsComponentSchema, templateComponentSchema, templateCopyCodeButtonSchema, templateCreateResponseSchema, templateCreateSchema, templateDeleteResponseSchema, templateDeleteSchema, templateFlowButtonSchema, templateFooterComponentSchema, templateHeaderComponentSchema, templateLanguageSchema, templateListResponseSchema, templateListSchema, templatePhoneNumberButtonSchema, templateQuickReplyButtonSchema, templateSchema, templateUpdateResponseSchema, templateUpdateSchema, templateUrlButtonSchema, webhookPayloadSchema };
|
|
1870
|
+
export { type BusinessAccountsListResponse, type ClientConfig, type DebugTokenResponse, type HandleOptions, type IncomingAudioMessage, type IncomingImageMessage, type IncomingMessage, type IncomingTextMessage, type MediaMetadata, type MessageContext, type MessageHandlers, type MessageResponse, type OutgoingImageMessage, type OutgoingLocationMessage, type OutgoingMessage, type OutgoingReactionMessage, type OutgoingTextMessage, type PhoneNumberListResponse, type SendImageInput, type SendLocationInput, type SendReactionInput, type SendTextInput, type Status, type Template, type TemplateBodyComponent, type TemplateButton, type TemplateButtonsComponent, type TemplateComponent, type TemplateCopyCodeButton, type TemplateCreate, type TemplateCreateResponse, type TemplateDelete, type TemplateDeleteResponse, type TemplateFlowButton, type TemplateFooterComponent, type TemplateHeaderComponent, type TemplateLanguage, type TemplateList, type TemplateListResponse, type TemplatePhoneNumberButton, type TemplateQuickReplyButton, type TemplateUpdate, type TemplateUpdateResponse, type TemplateUrlButton, type WebhookContext, type WebhookPayload, WhatsAppAPIError, WhatsAppClient, WhatsAppError, WhatsAppRateLimitError, WhatsAppValidationError, businessAccountResponseSchema, businessAccountsListResponseSchema, clientConfigSchema, debugTokenResponseSchema, incomingAudioMessageSchema, incomingImageMessageSchema, incomingMessageSchema, incomingTextMessageSchema, messageResponseSchema, outgoingImageMessageSchema, outgoingLocationMessageSchema, outgoingMessageSchema, outgoingReactionMessageSchema, outgoingTextMessageSchema, phoneNumberListResponseSchema, phoneNumberResponseSchema, sendImageInputSchema, sendLocationInputSchema, sendReactionInputSchema, sendTextInputSchema, statusSchema, templateBodyComponentSchema, templateButtonSchema, templateButtonsComponentSchema, templateComponentSchema, templateCopyCodeButtonSchema, templateCreateResponseSchema, templateCreateSchema, templateDeleteResponseSchema, templateDeleteSchema, templateFlowButtonSchema, templateFooterComponentSchema, templateHeaderComponentSchema, templateLanguageSchema, templateListResponseSchema, templateListSchema, templatePhoneNumberButtonSchema, templateQuickReplyButtonSchema, templateSchema, templateUpdateResponseSchema, templateUpdateSchema, templateUrlButtonSchema, webhookPayloadSchema };
|
package/dist/index.js
CHANGED
|
@@ -430,6 +430,24 @@ var MessagesService = class {
|
|
|
430
430
|
const client = this.getClient(phoneNumberId);
|
|
431
431
|
return sendReaction(client, input);
|
|
432
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Send any message type using the discriminated union
|
|
435
|
+
*
|
|
436
|
+
* @param message - Any outgoing message (text, image, location, reaction)
|
|
437
|
+
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
438
|
+
*/
|
|
439
|
+
async sendMessage(message, phoneNumberId) {
|
|
440
|
+
switch (message.type) {
|
|
441
|
+
case "text":
|
|
442
|
+
return this.sendText(message, phoneNumberId);
|
|
443
|
+
case "image":
|
|
444
|
+
return this.sendImage(message, phoneNumberId);
|
|
445
|
+
case "location":
|
|
446
|
+
return this.sendLocation(message, phoneNumberId);
|
|
447
|
+
case "reaction":
|
|
448
|
+
return this.sendReaction(message, phoneNumberId);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
433
451
|
};
|
|
434
452
|
|
|
435
453
|
// src/services/accounts/AccountsClient.ts
|
|
@@ -1328,33 +1346,6 @@ var WebhooksService = class {
|
|
|
1328
1346
|
extractStatuses(payload) {
|
|
1329
1347
|
return extractStatuses(payload);
|
|
1330
1348
|
}
|
|
1331
|
-
/**
|
|
1332
|
-
* Download media file by media ID
|
|
1333
|
-
*
|
|
1334
|
-
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
1335
|
-
* Uses the access token from the client configuration automatically.
|
|
1336
|
-
*
|
|
1337
|
-
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
1338
|
-
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
1339
|
-
* @throws Error if download fails or media ID is invalid
|
|
1340
|
-
*
|
|
1341
|
-
* @example
|
|
1342
|
-
* ```typescript
|
|
1343
|
-
* client.webhooks.handle(req.body, {
|
|
1344
|
-
* image: async (message, context) => {
|
|
1345
|
-
* const mediaData = await client.webhooks.downloadMedia(message.image.id);
|
|
1346
|
-
* // Upload to S3, save to disk, etc.
|
|
1347
|
-
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
1348
|
-
* },
|
|
1349
|
-
* });
|
|
1350
|
-
* ```
|
|
1351
|
-
*/
|
|
1352
|
-
async downloadMedia(mediaId) {
|
|
1353
|
-
if (!mediaId || mediaId.trim().length === 0) {
|
|
1354
|
-
throw new Error("Media ID is required");
|
|
1355
|
-
}
|
|
1356
|
-
return this.httpClient.getBinary(`/${mediaId}`);
|
|
1357
|
-
}
|
|
1358
1349
|
/**
|
|
1359
1350
|
* Validate webhook payload structure
|
|
1360
1351
|
*
|
|
@@ -1470,6 +1461,57 @@ var WebhooksService = class {
|
|
|
1470
1461
|
}
|
|
1471
1462
|
};
|
|
1472
1463
|
|
|
1464
|
+
// src/services/media/MediaService.ts
|
|
1465
|
+
var MediaService = class {
|
|
1466
|
+
constructor(httpClient) {
|
|
1467
|
+
this.httpClient = httpClient;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Download media file by media ID
|
|
1471
|
+
*
|
|
1472
|
+
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
1473
|
+
* Uses the access token from the client configuration automatically.
|
|
1474
|
+
*
|
|
1475
|
+
* According to WhatsApp API docs, you cannot download directly from the media ID endpoint.
|
|
1476
|
+
* The flow is:
|
|
1477
|
+
* 1. GET /MEDIA_ID → returns JSON metadata with a URL
|
|
1478
|
+
* 2. GET /MEDIA_URL → returns the actual binary data
|
|
1479
|
+
*
|
|
1480
|
+
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
1481
|
+
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
1482
|
+
* @throws Error if download fails or media ID is invalid
|
|
1483
|
+
*
|
|
1484
|
+
* @example
|
|
1485
|
+
* ```typescript
|
|
1486
|
+
* const mediaData = await client.media.download(message.image.id);
|
|
1487
|
+
* // Upload to S3, save to disk, etc.
|
|
1488
|
+
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
1489
|
+
* ```
|
|
1490
|
+
*/
|
|
1491
|
+
async download(mediaId) {
|
|
1492
|
+
if (!mediaId || mediaId.trim().length === 0) {
|
|
1493
|
+
throw new Error("Media ID is required");
|
|
1494
|
+
}
|
|
1495
|
+
const metadata = await this.httpClient.get(`/${mediaId}`);
|
|
1496
|
+
const response = await fetch(metadata.url, {
|
|
1497
|
+
method: "GET",
|
|
1498
|
+
headers: {
|
|
1499
|
+
Authorization: `Bearer ${this.httpClient.accessToken}`
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
if (!response.ok) {
|
|
1503
|
+
let errorMessage = `API Error: ${response.statusText}`;
|
|
1504
|
+
try {
|
|
1505
|
+
const error = await response.json();
|
|
1506
|
+
errorMessage = `API Error: ${error.error?.message || response.statusText} (${error.error?.code || response.status})`;
|
|
1507
|
+
} catch {
|
|
1508
|
+
}
|
|
1509
|
+
throw new Error(errorMessage);
|
|
1510
|
+
}
|
|
1511
|
+
return response.arrayBuffer();
|
|
1512
|
+
}
|
|
1513
|
+
};
|
|
1514
|
+
|
|
1473
1515
|
// src/client/WhatsAppClient.ts
|
|
1474
1516
|
import { ZodError as ZodError2 } from "zod";
|
|
1475
1517
|
var WhatsAppClient = class {
|
|
@@ -1478,6 +1520,7 @@ var WhatsAppClient = class {
|
|
|
1478
1520
|
business;
|
|
1479
1521
|
templates;
|
|
1480
1522
|
webhooks;
|
|
1523
|
+
media;
|
|
1481
1524
|
httpClient;
|
|
1482
1525
|
constructor(config) {
|
|
1483
1526
|
let validated;
|
|
@@ -1495,6 +1538,7 @@ var WhatsAppClient = class {
|
|
|
1495
1538
|
this.business = new BusinessService(this.httpClient);
|
|
1496
1539
|
this.templates = new TemplatesService(this.httpClient);
|
|
1497
1540
|
this.webhooks = new WebhooksService(this.httpClient);
|
|
1541
|
+
this.media = new MediaService(this.httpClient);
|
|
1498
1542
|
}
|
|
1499
1543
|
/**
|
|
1500
1544
|
* Debug the current access token
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whatsapp-cloud",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Work in progress. A WhatsApp client tailored for LLMs—built to actually work.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"zod": "^4.2.1"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
24
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
25
25
|
"lint": "tsc",
|
|
26
26
|
"example": "tsx src/examples/main.ts"
|
|
27
27
|
}
|
|
@@ -6,6 +6,7 @@ import { AccountsService } from "../services/accounts/index";
|
|
|
6
6
|
import { BusinessService } from "../services/business/index";
|
|
7
7
|
import { TemplatesService } from "../services/templates/index";
|
|
8
8
|
import { WebhooksService } from "../services/webhooks/index";
|
|
9
|
+
import { MediaService } from "../services/media/index";
|
|
9
10
|
import { ZodError } from "zod";
|
|
10
11
|
import { transformZodError } from "../utils/zod-error";
|
|
11
12
|
import type { DebugTokenResponse } from "../types/debug";
|
|
@@ -19,6 +20,7 @@ export class WhatsAppClient {
|
|
|
19
20
|
public readonly business: BusinessService;
|
|
20
21
|
public readonly templates: TemplatesService;
|
|
21
22
|
public readonly webhooks: WebhooksService;
|
|
23
|
+
public readonly media: MediaService;
|
|
22
24
|
|
|
23
25
|
private readonly httpClient: HttpClient;
|
|
24
26
|
|
|
@@ -43,6 +45,7 @@ export class WhatsAppClient {
|
|
|
43
45
|
this.business = new BusinessService(this.httpClient);
|
|
44
46
|
this.templates = new TemplatesService(this.httpClient);
|
|
45
47
|
this.webhooks = new WebhooksService(this.httpClient);
|
|
48
|
+
this.media = new MediaService(this.httpClient);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
/**
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { WhatsAppClient } from "../client";
|
|
3
|
+
import { writeFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
|
|
6
|
+
const client = new WhatsAppClient({
|
|
7
|
+
accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
async function testMediaDownload() {
|
|
11
|
+
try {
|
|
12
|
+
console.log("🧪 Testing Media Download API...\n");
|
|
13
|
+
|
|
14
|
+
// Media ID from the user's message
|
|
15
|
+
const mediaId = "768583052204368";
|
|
16
|
+
|
|
17
|
+
console.log(`📥 Downloading media with ID: ${mediaId}`);
|
|
18
|
+
console.log(" This should download the image from the message...\n");
|
|
19
|
+
|
|
20
|
+
// Download the media
|
|
21
|
+
const startTime = Date.now();
|
|
22
|
+
const mediaData = await client.media.download(mediaId);
|
|
23
|
+
const downloadTime = Date.now() - startTime;
|
|
24
|
+
|
|
25
|
+
console.log("✅ Download successful!");
|
|
26
|
+
console.log(` Size: ${mediaData.byteLength} bytes (${(mediaData.byteLength / 1024).toFixed(2)} KB)`);
|
|
27
|
+
console.log(` Time: ${downloadTime}ms\n`);
|
|
28
|
+
|
|
29
|
+
// Save to file for verification
|
|
30
|
+
const outputPath = join(process.cwd(), "downloaded-media.jpg");
|
|
31
|
+
const buffer = Buffer.from(mediaData);
|
|
32
|
+
writeFileSync(outputPath, buffer);
|
|
33
|
+
|
|
34
|
+
console.log(`💾 Saved to: ${outputPath}`);
|
|
35
|
+
console.log(" You can open this file to verify the download worked correctly.\n");
|
|
36
|
+
|
|
37
|
+
// Try to get metadata as well (if we had a getUrl method, but we can test the internal call)
|
|
38
|
+
console.log("📋 Testing metadata retrieval...");
|
|
39
|
+
try {
|
|
40
|
+
// We can test the internal get call by checking what we got
|
|
41
|
+
// Actually, let's just verify the download worked by checking file size
|
|
42
|
+
if (mediaData.byteLength > 0) {
|
|
43
|
+
console.log("✅ Media file is valid (non-empty)");
|
|
44
|
+
} else {
|
|
45
|
+
console.log("⚠️ Warning: Media file is empty");
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("❌ Error getting metadata:", error);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log("\n✨ Test completed successfully!");
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("\n❌ Test failed:");
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
console.error(` Error: ${error.message}`);
|
|
56
|
+
console.error(` Stack: ${error.stack}`);
|
|
57
|
+
} else {
|
|
58
|
+
console.error(" Unknown error:", error);
|
|
59
|
+
}
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
testMediaDownload();
|
|
65
|
+
|
package/src/services/index.ts
CHANGED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { HttpClient } from "../../client/HttpClient";
|
|
2
|
+
import type { MediaMetadata } from "../../types/media";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Media service for downloading WhatsApp media files
|
|
6
|
+
*
|
|
7
|
+
* This service handles downloading media files from WhatsApp servers.
|
|
8
|
+
*/
|
|
9
|
+
export class MediaService {
|
|
10
|
+
constructor(private readonly httpClient: HttpClient) {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Download media file by media ID
|
|
14
|
+
*
|
|
15
|
+
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
16
|
+
* Uses the access token from the client configuration automatically.
|
|
17
|
+
*
|
|
18
|
+
* According to WhatsApp API docs, you cannot download directly from the media ID endpoint.
|
|
19
|
+
* The flow is:
|
|
20
|
+
* 1. GET /MEDIA_ID → returns JSON metadata with a URL
|
|
21
|
+
* 2. GET /MEDIA_URL → returns the actual binary data
|
|
22
|
+
*
|
|
23
|
+
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
24
|
+
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
25
|
+
* @throws Error if download fails or media ID is invalid
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const mediaData = await client.media.download(message.image.id);
|
|
30
|
+
* // Upload to S3, save to disk, etc.
|
|
31
|
+
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
async download(mediaId: string): Promise<ArrayBuffer> {
|
|
35
|
+
if (!mediaId || mediaId.trim().length === 0) {
|
|
36
|
+
throw new Error("Media ID is required");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Step 1: Get the media URL and metadata
|
|
40
|
+
// GET /MEDIA_ID returns JSON, not binary, so we use get() not getBinary()
|
|
41
|
+
// phone_number_id query param is optional per WhatsApp docs - we omit it
|
|
42
|
+
const metadata = await this.httpClient.get<MediaMetadata>(`/${mediaId}`);
|
|
43
|
+
|
|
44
|
+
// Step 2: Download the actual binary data from the URL
|
|
45
|
+
// The URL is a full URL (not a path), so we can't use httpClient.getBinary()
|
|
46
|
+
// which expects a path. We need to fetch the full URL directly.
|
|
47
|
+
const response = await fetch(metadata.url, {
|
|
48
|
+
method: "GET",
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${this.httpClient.accessToken}`,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
let errorMessage = `API Error: ${response.statusText}`;
|
|
56
|
+
try {
|
|
57
|
+
const error = (await response.json()) as {
|
|
58
|
+
error?: { message?: string; code?: number };
|
|
59
|
+
};
|
|
60
|
+
errorMessage = `API Error: ${
|
|
61
|
+
error.error?.message || response.statusText
|
|
62
|
+
} (${error.error?.code || response.status})`;
|
|
63
|
+
} catch {
|
|
64
|
+
// If JSON parsing fails, use default message
|
|
65
|
+
}
|
|
66
|
+
throw new Error(errorMessage);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return response.arrayBuffer();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MediaService } from "./MediaService";
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
SendImageInput,
|
|
11
11
|
SendLocationInput,
|
|
12
12
|
SendReactionInput,
|
|
13
|
+
OutgoingMessage,
|
|
13
14
|
} from "../../types/messages/outgoing";
|
|
14
15
|
import type { MessageResponse } from "../../types/messages/response";
|
|
15
16
|
|
|
@@ -94,4 +95,26 @@ export class MessagesService {
|
|
|
94
95
|
const client = this.getClient(phoneNumberId);
|
|
95
96
|
return sendReaction(client, input);
|
|
96
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Send any message type using the discriminated union
|
|
101
|
+
*
|
|
102
|
+
* @param message - Any outgoing message (text, image, location, reaction)
|
|
103
|
+
* @param phoneNumberId - Optional phone number ID (overrides client config)
|
|
104
|
+
*/
|
|
105
|
+
async sendMessage(
|
|
106
|
+
message: OutgoingMessage,
|
|
107
|
+
phoneNumberId?: string
|
|
108
|
+
): Promise<MessageResponse> {
|
|
109
|
+
switch (message.type) {
|
|
110
|
+
case "text":
|
|
111
|
+
return this.sendText(message, phoneNumberId);
|
|
112
|
+
case "image":
|
|
113
|
+
return this.sendImage(message, phoneNumberId);
|
|
114
|
+
case "location":
|
|
115
|
+
return this.sendLocation(message, phoneNumberId);
|
|
116
|
+
case "reaction":
|
|
117
|
+
return this.sendReaction(message, phoneNumberId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
97
120
|
}
|
|
@@ -168,37 +168,6 @@ export class WebhooksService {
|
|
|
168
168
|
return extractStatuses(payload);
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
/**
|
|
172
|
-
* Download media file by media ID
|
|
173
|
-
*
|
|
174
|
-
* Downloads media files (images, audio, video, documents) from WhatsApp servers.
|
|
175
|
-
* Uses the access token from the client configuration automatically.
|
|
176
|
-
*
|
|
177
|
-
* @param mediaId - Media ID from incoming message (e.g., message.image.id, message.audio.id)
|
|
178
|
-
* @returns Promise resolving to ArrayBuffer containing the media file
|
|
179
|
-
* @throws Error if download fails or media ID is invalid
|
|
180
|
-
*
|
|
181
|
-
* @example
|
|
182
|
-
* ```typescript
|
|
183
|
-
* client.webhooks.handle(req.body, {
|
|
184
|
-
* image: async (message, context) => {
|
|
185
|
-
* const mediaData = await client.webhooks.downloadMedia(message.image.id);
|
|
186
|
-
* // Upload to S3, save to disk, etc.
|
|
187
|
-
* await s3.upload({ key: message.image.id, body: Buffer.from(mediaData) });
|
|
188
|
-
* },
|
|
189
|
-
* });
|
|
190
|
-
* ```
|
|
191
|
-
*/
|
|
192
|
-
async downloadMedia(mediaId: string): Promise<ArrayBuffer> {
|
|
193
|
-
if (!mediaId || mediaId.trim().length === 0) {
|
|
194
|
-
throw new Error("Media ID is required");
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// WhatsApp API endpoint: GET /{version}/{media-id}
|
|
198
|
-
// Use HttpClient's getBinary method which handles baseURL, apiVersion, and auth automatically
|
|
199
|
-
return this.httpClient.getBinary(`/${mediaId}`);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
171
|
/**
|
|
203
172
|
* Validate webhook payload structure
|
|
204
173
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { WebhookPayload } from "../../../types/webhooks";
|
|
2
|
-
import type { IncomingMessage } from "../../../types/
|
|
2
|
+
import type { IncomingMessage } from "../../../types/messages/incoming";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Extract all incoming messages from webhook payload
|
package/src/types/index.ts
CHANGED