core-services-sdk 1.3.49 → 1.3.51

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.
@@ -0,0 +1,301 @@
1
+ import { post } from '../../http/http.js'
2
+
3
+ const TELEGRAM_API_BASE_URL = 'https://api.telegram.org'
4
+
5
+ /**
6
+ * Builds a full set of Telegram Bot API endpoint URLs
7
+ * based on the provided bot token and an optional base URL.
8
+ *
9
+ * This helper centralizes all Telegram endpoints used by the system,
10
+ * making it easier to mock, override, or customize for testing environments.
11
+ *
12
+ * @typedef {Object} TelegramApiUrls
13
+ * @property {string} SEND_MESSAGE
14
+ * URL to send a text message to a chat using the Telegram Bot API.
15
+ *
16
+ * @property {string} FORWARD_MESSAGE
17
+ * URL to forward an existing message from one chat to another.
18
+ *
19
+ * @property {string} SEND_PHOTO
20
+ * URL to send a photo to a chat.
21
+ *
22
+ * @property {string} SEND_AUDIO
23
+ * URL to send an audio file.
24
+ *
25
+ * @property {string} SEND_DOCUMENT
26
+ * URL to send files such as PDF, DOC, ZIP, and others supported by Telegram.
27
+ *
28
+ * @property {string} SEND_STICKER
29
+ * URL to send a sticker.
30
+ *
31
+ * @property {string} SEND_VIDEO
32
+ * URL to send a video file.
33
+ *
34
+ * @property {string} SEND_VOICE
35
+ * URL to send a voice note.
36
+ *
37
+ * @property {string} SEND_LOCATION
38
+ * URL to send a geolocation point.
39
+ *
40
+ * @property {string} SEND_CHAT_ACTION
41
+ * URL to send a chat action (typing, uploading photo, etc).
42
+ *
43
+ * @property {string} GET_USER_PROFILE_PHOTOS
44
+ * URL to retrieve the profile photos of a specific user.
45
+ *
46
+ * @property {string} GET_UPDATES
47
+ * URL to poll for new updates (not used when using webhooks).
48
+ *
49
+ * @property {string} GET_FILE
50
+ * URL to fetch a file path for downloading a file uploaded to Telegram servers.
51
+ */
52
+
53
+ /**
54
+ * Generates Telegram Bot API endpoint URLs for the given bot token.
55
+ *
56
+ * @param {Object} params
57
+ * @param {string} params.token
58
+ * The bot token obtained from BotFather.
59
+ *
60
+ * @param {string} [params.telegramBaseUrl=TELEGRAM_API_BASE_URL]
61
+ * Optional override for the Telegram API base URL.
62
+ * Useful for testing or for proxying requests.
63
+ *
64
+ * @returns {TelegramApiUrls}
65
+ * A dictionary of fully resolved Telegram API endpoint URLs.
66
+ */
67
+ export const getTelegramApiUrls = ({
68
+ token,
69
+ telegramBaseUrl = TELEGRAM_API_BASE_URL,
70
+ }) => ({
71
+ SEND_MESSAGE: `${telegramBaseUrl}/bot${token}/sendMessage`,
72
+ FORWARD_MESSAGE: `${telegramBaseUrl}/bot${token}/forwardMessage`,
73
+ SEND_PHOTO: `${telegramBaseUrl}/bot${token}/sendPhoto`,
74
+ SEND_AUDIO: `${telegramBaseUrl}/bot${token}/sendAudio`,
75
+ SEND_DOCUMENT: `${telegramBaseUrl}/bot${token}/sendDocument`,
76
+ SEND_STICKER: `${telegramBaseUrl}/bot${token}/sendSticker`,
77
+ SEND_VIDEO: `${telegramBaseUrl}/bot${token}/sendVideo`,
78
+ SEND_VOICE: `${telegramBaseUrl}/bot${token}/sendVoice`,
79
+ SEND_LOCATION: `${telegramBaseUrl}/bot${token}/sendLocation`,
80
+ SEND_CHAT_ACTION: `${telegramBaseUrl}/bot${token}/sendChatAction`,
81
+ GET_USER_PROFILE_PHOTOS: `${telegramBaseUrl}/bot${token}/getUserProfilePhotos`,
82
+ GET_UPDATES: `${telegramBaseUrl}/bot${token}/getUpdates`,
83
+ GET_FILE: `${telegramBaseUrl}/bot${token}/getFile`,
84
+ })
85
+
86
+ /**
87
+ * Factory that creates a set of high level Telegram Bot API helper methods.
88
+ *
89
+ * Each method sends a specific type of message (text, photo, video, document)
90
+ * through the Telegram Bot API using the provided bot token.
91
+ *
92
+ * This abstraction wraps the raw URL generation logic and HTTP calls,
93
+ * allowing higher level services to use clean method calls instead of
94
+ * managing endpoint URLs manually.
95
+ *
96
+ * @typedef {Object} TelegramApis
97
+ *
98
+ * @property {Function} sendMessage
99
+ * Sends a text message to a specific chat.
100
+ *
101
+ * @property {Function} sendButtonsGroup
102
+ * Sends a text message with an inline keyboard button group.
103
+ *
104
+ * @property {Function} sendPhoto
105
+ * Sends a photo with an optional caption.
106
+ *
107
+ * @property {Function} sendVideo
108
+ * Sends a video with an optional caption.
109
+ *
110
+ * @property {Function} sendAudio
111
+ * Sends an audio file with an optional caption.
112
+ *
113
+ * @property {Function} sendDocument
114
+ * Sends a document file with an optional caption.
115
+ */
116
+
117
+ /**
118
+ * Creates Telegram API methods bound to a specific bot token.
119
+ *
120
+ * @param {Object} params
121
+ * @param {string} params.token
122
+ * Telegram bot token obtained from BotFather.
123
+ *
124
+ * @returns {TelegramApis}
125
+ * An object containing all supported Telegram message sending functions.
126
+ */
127
+ export const telegramApis = ({ token }) => {
128
+ const APIS = getTelegramApiUrls({ token })
129
+
130
+ return {
131
+ /**
132
+ * Sends a text message to a Telegram chat.
133
+ *
134
+ * @param {Object} params
135
+ * @param {string} params.text
136
+ * The message content.
137
+ *
138
+ * @param {number|string} params.chatId
139
+ * Chat identifier where the message should be sent.
140
+ *
141
+ * @param {Array<Object>} [params.entities]
142
+ * Optional entities for formatting (bold, URL, etc).
143
+ *
144
+ * @returns {Promise<import('../../http/http.js').HttpResponse>}
145
+ * Telegram API response.
146
+ */
147
+ async sendMessage({ text, chatId, entities }) {
148
+ const res = await post({
149
+ url: APIS.SEND_MESSAGE,
150
+ body: {
151
+ chat_id: chatId,
152
+ text,
153
+ entities,
154
+ },
155
+ })
156
+ return res
157
+ },
158
+
159
+ /**
160
+ * Sends a text message with inline keyboard buttons.
161
+ *
162
+ * @param {Object} params
163
+ * @param {string} params.text
164
+ * The message content.
165
+ *
166
+ * @param {number|string} params.chatId
167
+ * Chat identifier.
168
+ *
169
+ * @param {Array<Array<Object>>} params.options
170
+ * Two dimensional array of inline keyboard button objects.
171
+ *
172
+ * @returns {Promise<import('../../http/http.js').HttpResponse>}
173
+ * Telegram API response.
174
+ */
175
+ async sendButtonsGroup({ text, chatId, options }) {
176
+ const res = await post({
177
+ url: APIS.SEND_MESSAGE,
178
+ body: {
179
+ chat_id: chatId,
180
+ text,
181
+ reply_markup: {
182
+ inline_keyboard: options,
183
+ },
184
+ },
185
+ })
186
+ return res
187
+ },
188
+
189
+ /**
190
+ * Sends a photo message using an HTTP URL.
191
+ *
192
+ * @param {Object} params
193
+ * @param {number|string} params.chatId
194
+ * Chat identifier.
195
+ *
196
+ * @param {string} params.photo
197
+ * Publicly accessible HTTP URL of the photo.
198
+ *
199
+ * @param {string} [params.caption]
200
+ * Optional caption for the photo.
201
+ *
202
+ * @returns {Promise<import('../../http/http.js').HttpResponse>}
203
+ * Telegram API response.
204
+ */
205
+ async sendPhoto({ caption, photo, chatId }) {
206
+ const res = await post({
207
+ url: APIS.SEND_PHOTO,
208
+ body: {
209
+ chat_id: chatId,
210
+ caption,
211
+ photo,
212
+ },
213
+ })
214
+ return res
215
+ },
216
+
217
+ /**
218
+ * Sends a video message using an HTTP URL.
219
+ *
220
+ * @param {Object} params
221
+ * @param {number|string} params.chatId
222
+ * Chat identifier.
223
+ *
224
+ * @param {string} params.video
225
+ * Public video URL.
226
+ *
227
+ * @param {string} [params.caption]
228
+ * Optional caption.
229
+ *
230
+ * @returns {Promise<import('../../http/http.js').HttpResponse>}
231
+ * Telegram API response.
232
+ */
233
+ async sendVideo({ caption, video, chatId }) {
234
+ const res = await post({
235
+ url: APIS.SEND_VIDEO,
236
+ body: {
237
+ chat_id: chatId,
238
+ caption,
239
+ video,
240
+ },
241
+ })
242
+ return res
243
+ },
244
+
245
+ /**
246
+ * Sends an audio message using an HTTP URL.
247
+ *
248
+ * @param {Object} params
249
+ * @param {number|string} params.chatId
250
+ * Chat identifier.
251
+ *
252
+ * @param {string} params.audio
253
+ * Public audio URL.
254
+ *
255
+ * @param {string} [params.caption]
256
+ * Optional caption.
257
+ *
258
+ * @returns {Promise<import('../../http/http.js').HttpResponse>}
259
+ * Telegram API response.
260
+ */
261
+ async sendAudio({ caption, audio, chatId }) {
262
+ const res = await post({
263
+ url: APIS.SEND_AUDIO,
264
+ body: {
265
+ chat_id: chatId,
266
+ caption,
267
+ audio,
268
+ },
269
+ })
270
+ return res
271
+ },
272
+
273
+ /**
274
+ * Sends a document file using an HTTP URL.
275
+ *
276
+ * @param {Object} params
277
+ * @param {number|string} params.chatId
278
+ * Chat identifier.
279
+ *
280
+ * @param {string} params.document
281
+ * URL to the document file.
282
+ *
283
+ * @param {string} [params.caption]
284
+ * Optional caption.
285
+ *
286
+ * @returns {Promise<import('../../http/http.js').HttpResponse>}
287
+ * Telegram API response.
288
+ */
289
+ async sendDocument({ caption, document, chatId }) {
290
+ const res = await post({
291
+ url: APIS.SEND_DOCUMENT,
292
+ body: {
293
+ chat_id: chatId,
294
+ caption,
295
+ document,
296
+ },
297
+ })
298
+ return res
299
+ },
300
+ }
301
+ }
@@ -0,0 +1,401 @@
1
+ import { post } from '../../http/http.js'
2
+
3
+ const WHATSAPP_API_BASE_URL = 'https://graph.facebook.com'
4
+
5
+ /**
6
+ * Retrieves metadata for a WhatsApp media object.
7
+ *
8
+ * WhatsApp Cloud API does not provide the media file directly via mediaId.
9
+ * Instead, you must first request the metadata for the media item, which
10
+ * includes a temporary download URL. This URL can then be used to retrieve
11
+ * the actual binary content of the file.
12
+ *
13
+ * The returned metadata object typically includes:
14
+ * - `url` A temporary URL that allows downloading the media file
15
+ * - `mime_type` The detected MIME type of the media
16
+ * - `id` The mediaId itself
17
+ *
18
+ * Example output from WhatsApp:
19
+ * {
20
+ * "url": "https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=...",
21
+ * "mime_type": "image/jpeg",
22
+ * "id": "MEDIA_ID"
23
+ * }
24
+ *
25
+ * Note:
26
+ * The temporary download URL is short-lived and must be accessed quickly.
27
+ *
28
+ * @param {Object} params
29
+ * @param {string} params.mediaId
30
+ * The media identifier received in an incoming WhatsApp webhook message.
31
+ *
32
+ * @param {string} params.token
33
+ * WhatsApp Cloud API access token used for authorization.
34
+ *
35
+ * @param {string} [params.version='v21.0']
36
+ * The WhatsApp Cloud API Graph version to use.
37
+ *
38
+ * @returns {Promise<Object>}
39
+ * Resolves with the media metadata object containing `url`, `mime_type`, and `id`.
40
+ *
41
+ * @throws {Error}
42
+ * If the metadata request fails or WhatsApp responds with a non successful status code.
43
+ */
44
+ export const getWhatsAppMediaInfo = async ({
45
+ token,
46
+ mediaId,
47
+ version = 'v21.0',
48
+ baseUrl = WHATSAPP_API_BASE_URL,
49
+ }) => {
50
+ const url = `${baseUrl}/${version}/${mediaId}`
51
+
52
+ const res = await fetch(url, {
53
+ headers: {
54
+ Authorization: `Bearer ${token}`,
55
+ },
56
+ })
57
+
58
+ if (!res.ok) {
59
+ throw new Error(`Failed to retrieve media info: ${res.status}`)
60
+ }
61
+
62
+ return res.json()
63
+ }
64
+
65
+ /**
66
+ * Downloads WhatsApp media and returns either a Buffer or a Stream,
67
+ * depending on mode.
68
+ *
69
+ * @param {Object} params
70
+ * @param {string} params.mediaId
71
+ * @param {string} params.token
72
+ * @param {'buffer' | 'stream'} params.mode
73
+ * @returns {Promise<Buffer|ReadableStream>}
74
+ */
75
+ export const downloadWhatsAppMedia = async ({
76
+ token,
77
+ mediaId,
78
+ mode = 'buffer',
79
+ }) => {
80
+ const info = await getWhatsAppMediaInfo({ mediaId, token })
81
+ const { url: downloadUrl } = info
82
+
83
+ const res = await fetch(downloadUrl, {
84
+ headers: {
85
+ Authorization: `Bearer ${token}`,
86
+ },
87
+ })
88
+
89
+ if (!res.ok) {
90
+ throw new Error(`Failed to download media: ${res.status}`)
91
+ }
92
+
93
+ if (mode === 'stream') {
94
+ return res.body
95
+ }
96
+
97
+ const buffer = Buffer.from(await res.arrayBuffer())
98
+ return buffer
99
+ }
100
+
101
+ /**
102
+ * Builds the WhatsApp Cloud API messages endpoint URL.
103
+ *
104
+ * This function generates the full URL required to send messages
105
+ * through the WhatsApp Cloud API. It concatenates the base Graph API URL,
106
+ * the selected API version, and the phone number ID to produce:
107
+ *
108
+ * https://graph.facebook.com/{version}/{phoneNumberId}/messages
109
+ *
110
+ * The returned URL can then be used for POST requests to send text,
111
+ * media, interactive messages, and more.
112
+ *
113
+ * @param {Object} params
114
+ * @param {string} params.phoneNumberId
115
+ * The WhatsApp phone number ID associated with the business account.
116
+ *
117
+ * @param {string} [params.version='v21.0']
118
+ * The WhatsApp Cloud API version to use. Defaults to the current stable.
119
+ *
120
+ * @param {string} [params.baseUrl=WHATSAPP_API_BASE_URL]
121
+ * Optional override for the Graph API base URL (useful for testing or proxying).
122
+ *
123
+ * @returns {string}
124
+ * Fully resolved WhatsApp Cloud API endpoint URL for sending messages.
125
+ */
126
+ export const getWhatsAppApiUrls = ({
127
+ phoneNumberId,
128
+ version = 'v21.0',
129
+ baseUrl = WHATSAPP_API_BASE_URL,
130
+ }) => `${baseUrl}/${version}/${phoneNumberId}/messages`
131
+
132
+ /**
133
+ * Factory that creates WhatsApp Cloud API helper methods.
134
+ *
135
+ * This module wraps the WhatsApp Graph API endpoints and exposes
136
+ * high level functions for sending text messages, interactive buttons,
137
+ * images, videos, documents, and audio files.
138
+ *
139
+ * Each returned method builds the correct request format according
140
+ * to the WhatsApp Cloud API specification.
141
+ *
142
+ * @typedef {Object} WhatsAppApis
143
+ *
144
+ * @property {Function} sendMessage
145
+ * Sends a plain text message to an individual WhatsApp user.
146
+ *
147
+ * @property {Function} sendButtonsGroup
148
+ * Sends an interactive message containing buttons.
149
+ *
150
+ * @property {Function} sendPhoto
151
+ * Sends an image message using a public URL.
152
+ *
153
+ * @property {Function} sendVideo
154
+ * Sends a video file using a public URL.
155
+ *
156
+ * @property {Function} sendDocument
157
+ * Sends a document message using a public URL.
158
+ *
159
+ * @property {Function} sendAudio
160
+ * Sends an audio file using a public URL.
161
+ */
162
+
163
+ /**
164
+ * Creates a WhatsApp API client bound to a specific token, phone number ID,
165
+ * and Graph API version.
166
+ *
167
+ * @param {Object} params
168
+ * @param {string} params.token
169
+ * WhatsApp Cloud API access token.
170
+ *
171
+ * @param {string} params.phoneNumberId
172
+ * The phone number ID from Meta Business Manager used for sending messages.
173
+ *
174
+ * @param {string} [params.version='v21.0']
175
+ * WhatsApp Graph API version to use.
176
+ *
177
+ * @returns {WhatsAppApis}
178
+ * A set of helper functions for interacting with the WhatsApp Cloud API.
179
+ */
180
+ export const whatsappApis = ({ token, phoneNumberId, version = 'v21.0' }) => {
181
+ const url = getWhatsAppApiUrls({ phoneNumberId, version })
182
+
183
+ const headers = {
184
+ Authorization: `Bearer ${token}`,
185
+ }
186
+
187
+ const bodyBase = {
188
+ recipient_type: 'individual',
189
+ messaging_product: 'whatsapp',
190
+ }
191
+
192
+ return {
193
+ /**
194
+ * Sends a text message to an individual WhatsApp user.
195
+ *
196
+ * @param {Object} params
197
+ * @param {string} params.chatId
198
+ * The WhatsApp number (in international format) of the recipient.
199
+ *
200
+ * @param {string} params.text
201
+ * The message content to send.
202
+ *
203
+ * @param {boolean} [params.preview_url=true]
204
+ * Whether URL previews should be generated automatically.
205
+ *
206
+ * @returns {Promise<import('bot-services-libs-shared').HttpResponse>}
207
+ * The API response from the WhatsApp Cloud API.
208
+ */
209
+ async sendMessage({ chatId, text, preview_url = true }) {
210
+ const textMessage = {
211
+ to: chatId,
212
+ type: 'text',
213
+ text: {
214
+ preview_url,
215
+ body: text,
216
+ },
217
+ }
218
+
219
+ const res = await post({
220
+ url,
221
+ headers,
222
+ body: {
223
+ ...bodyBase,
224
+ ...textMessage,
225
+ },
226
+ })
227
+
228
+ return res
229
+ },
230
+
231
+ /**
232
+ * Sends an interactive buttons message to a WhatsApp user.
233
+ *
234
+ * @param {Object} params
235
+ * @param {string} params.chatId
236
+ * The recipient phone number.
237
+ *
238
+ * @param {Object} params.buttonsBody
239
+ * The full interactive object containing button definitions.
240
+ * The caller is expected to pass a structure matching
241
+ * WhatsApp's interactive message schema.
242
+ *
243
+ * @returns {Promise<import('bot-services-libs-shared').HttpResponse>}
244
+ * The API response.
245
+ */
246
+ async sendButtonsGroup({ chatId, buttonsBody }) {
247
+ const res = await post({
248
+ url,
249
+ headers,
250
+ body: {
251
+ ...bodyBase,
252
+ to: chatId,
253
+ type: 'interactive',
254
+ ...buttonsBody,
255
+ },
256
+ })
257
+
258
+ return res
259
+ },
260
+
261
+ /**
262
+ * Sends an image message using a public URL.
263
+ *
264
+ * @param {Object} params
265
+ * @param {string} params.chatId
266
+ * The phone number of the recipient.
267
+ *
268
+ * @param {string} params.photo
269
+ * Public URL of the image.
270
+ *
271
+ * @param {string} [params.caption]
272
+ * Optional caption added to the image.
273
+ *
274
+ * @returns {Promise<import('bot-services-libs-shared').HttpResponse>}
275
+ * The API response.
276
+ */
277
+ async sendPhoto({ caption, photo, chatId }) {
278
+ const res = await post({
279
+ url,
280
+ headers,
281
+ body: {
282
+ ...bodyBase,
283
+ to: chatId,
284
+ type: 'image',
285
+ image: {
286
+ link: photo,
287
+ caption,
288
+ },
289
+ },
290
+ })
291
+
292
+ return res
293
+ },
294
+
295
+ /**
296
+ * Sends a video message using a public URL.
297
+ *
298
+ * @param {Object} params
299
+ * @param {string} params.chatId
300
+ * Recipient phone number.
301
+ *
302
+ * @param {string} params.video
303
+ * Public URL to the video file.
304
+ *
305
+ * @param {string} [params.caption]
306
+ * Optional caption added to the video.
307
+ *
308
+ * @param {string} [params.filename]
309
+ * Optional filename displayed to the user.
310
+ *
311
+ * @returns {Promise<import('bot-services-libs-shared').HttpResponse>}
312
+ * The API response.
313
+ */
314
+ async sendVideo({ caption, video, filename, chatId }) {
315
+ const res = await post({
316
+ url,
317
+ headers,
318
+ body: {
319
+ ...bodyBase,
320
+ to: chatId,
321
+ type: 'video',
322
+ video: {
323
+ link: video,
324
+ caption,
325
+ ...(filename ? { filename } : null),
326
+ },
327
+ },
328
+ })
329
+
330
+ return res
331
+ },
332
+
333
+ /**
334
+ * Sends a document file using a public URL.
335
+ *
336
+ * @param {Object} params
337
+ * @param {string} params.chatId
338
+ * Recipient WhatsApp number.
339
+ *
340
+ * @param {string} params.document
341
+ * Public URL to the document.
342
+ *
343
+ * @param {string} [params.caption]
344
+ * Optional text caption.
345
+ *
346
+ * @param {string} [params.filename]
347
+ * Optional filename shown to the user.
348
+ *
349
+ * @returns {Promise<import('bot-services-libs-shared').HttpResponse>}
350
+ * The API response.
351
+ */
352
+ async sendDocument({ caption, document, filename, chatId }) {
353
+ const res = await post({
354
+ url,
355
+ headers,
356
+ body: {
357
+ ...bodyBase,
358
+ to: chatId,
359
+ type: 'document',
360
+ document: {
361
+ link: document,
362
+ caption,
363
+ ...(filename ? { filename } : null),
364
+ },
365
+ },
366
+ })
367
+
368
+ return res
369
+ },
370
+
371
+ /**
372
+ * Sends an audio message using a public URL.
373
+ *
374
+ * @param {Object} params
375
+ * @param {string} params.chatId
376
+ * The phone number of the recipient.
377
+ *
378
+ * @param {string} params.audio
379
+ * Public URL to the audio file.
380
+ *
381
+ * @returns {Promise<import('bot-services-libs-shared').HttpResponse>}
382
+ * The API response.
383
+ */
384
+ async sendAudio({ audio, chatId }) {
385
+ const res = await post({
386
+ url,
387
+ headers,
388
+ body: {
389
+ ...bodyBase,
390
+ to: chatId,
391
+ type: 'audio',
392
+ audio: {
393
+ link: audio,
394
+ },
395
+ },
396
+ })
397
+
398
+ return res
399
+ },
400
+ }
401
+ }