instagram-graph-api-sdk 1.0.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/LICENSE +200 -0
- package/README.md +279 -0
- package/dist/index.d.mts +2225 -0
- package/dist/index.d.ts +2225 -0
- package/dist/index.js +1745 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1696 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1745 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var axios = require('axios');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
9
|
+
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
10
|
+
|
|
11
|
+
// src/http.ts
|
|
12
|
+
|
|
13
|
+
// src/endpoints.ts
|
|
14
|
+
var INSTAGRAM_BASE_URL = "https://graph.instagram.com";
|
|
15
|
+
function buildUrl(version, path) {
|
|
16
|
+
return `${INSTAGRAM_BASE_URL}/${version}${path}`;
|
|
17
|
+
}
|
|
18
|
+
var USER_ENDPOINTS = {
|
|
19
|
+
/** Get user profile: GET /{user-id} */
|
|
20
|
+
PROFILE: (userId) => `/${userId}`,
|
|
21
|
+
/** Get user media: GET /{user-id}/media */
|
|
22
|
+
MEDIA: (userId) => `/${userId}/media`,
|
|
23
|
+
/** Get user stories: GET /{user-id}/stories */
|
|
24
|
+
STORIES: (userId) => `/${userId}/stories`,
|
|
25
|
+
/** Get user insights: GET /{user-id}/insights */
|
|
26
|
+
INSIGHTS: (userId) => `/${userId}/insights`,
|
|
27
|
+
/** Get live media: GET /{user-id}/live_media */
|
|
28
|
+
LIVE_MEDIA: (userId) => `/${userId}/live_media`,
|
|
29
|
+
/** Get content publishing limit: GET /{user-id}/content_publishing_limit */
|
|
30
|
+
CONTENT_PUBLISHING_LIMIT: (userId) => `/${userId}/content_publishing_limit`,
|
|
31
|
+
/** Get business discovery: GET /{user-id}?fields=business_discovery.username(...) */
|
|
32
|
+
BUSINESS_DISCOVERY: (userId) => `/${userId}`,
|
|
33
|
+
/** Get mentioned media: GET /{user-id}/mentioned_media */
|
|
34
|
+
MENTIONED_MEDIA: (userId) => `/${userId}/mentioned_media`,
|
|
35
|
+
/** Get mentioned comment: GET /{user-id}/mentioned_comment */
|
|
36
|
+
MENTIONED_COMMENT: (userId) => `/${userId}/mentioned_comment`,
|
|
37
|
+
/** Get tags: GET /{user-id}/tags */
|
|
38
|
+
TAGS: (userId) => `/${userId}/tags`,
|
|
39
|
+
/** Get recently searched hashtags: GET /{user-id}/recently_searched_hashtags */
|
|
40
|
+
RECENTLY_SEARCHED_HASHTAGS: (userId) => `/${userId}/recently_searched_hashtags`,
|
|
41
|
+
/** Get available catalogs: GET /{user-id}/available_catalogs */
|
|
42
|
+
AVAILABLE_CATALOGS: (userId) => `/${userId}/available_catalogs`,
|
|
43
|
+
/** Search catalog products: GET /{user-id}/catalog_product_search */
|
|
44
|
+
CATALOG_PRODUCT_SEARCH: (userId) => `/${userId}/catalog_product_search`
|
|
45
|
+
};
|
|
46
|
+
var MEDIA_ENDPOINTS = {
|
|
47
|
+
/** Get media by ID: GET /{media-id} */
|
|
48
|
+
GET: (mediaId) => `/${mediaId}`,
|
|
49
|
+
/** Get media children (carousel): GET /{media-id}/children */
|
|
50
|
+
CHILDREN: (mediaId) => `/${mediaId}/children`,
|
|
51
|
+
/** Get media comments: GET /{media-id}/comments */
|
|
52
|
+
COMMENTS: (mediaId) => `/${mediaId}/comments`,
|
|
53
|
+
/** Get media insights: GET /{media-id}/insights */
|
|
54
|
+
INSIGHTS: (mediaId) => `/${mediaId}/insights`,
|
|
55
|
+
/** Get media collaborators: GET /{media-id}/collaborators */
|
|
56
|
+
COLLABORATORS: (mediaId) => `/${mediaId}/collaborators`,
|
|
57
|
+
/** Get product tags: GET /{media-id}/product_tags */
|
|
58
|
+
PRODUCT_TAGS: (mediaId) => `/${mediaId}/product_tags`
|
|
59
|
+
};
|
|
60
|
+
var PUBLISHING_ENDPOINTS = {
|
|
61
|
+
/** Create media container: POST /{user-id}/media */
|
|
62
|
+
CREATE_CONTAINER: (userId) => `/${userId}/media`,
|
|
63
|
+
/** Publish media: POST /{user-id}/media_publish */
|
|
64
|
+
PUBLISH: (userId) => `/${userId}/media_publish`,
|
|
65
|
+
/** Get container status: GET /{container-id} */
|
|
66
|
+
CONTAINER_STATUS: (containerId) => `/${containerId}`
|
|
67
|
+
};
|
|
68
|
+
var MESSAGING_ENDPOINTS = {
|
|
69
|
+
/** Send message: POST /{user-id}/messages */
|
|
70
|
+
SEND: (userId) => `/${userId}/messages`,
|
|
71
|
+
/** Get conversations: GET /{user-id}/conversations */
|
|
72
|
+
CONVERSATIONS: (userId) => `/${userId}/conversations`,
|
|
73
|
+
/** Get conversation by ID: GET /{conversation-id} */
|
|
74
|
+
CONVERSATION: (conversationId) => `/${conversationId}`,
|
|
75
|
+
/** Get message by ID: GET /{message-id} */
|
|
76
|
+
MESSAGE: (messageId) => `/${messageId}`
|
|
77
|
+
};
|
|
78
|
+
var WELCOME_FLOW_ENDPOINTS = {
|
|
79
|
+
/** Get/Create/Update flows: /{user-id}/welcome_message_flows */
|
|
80
|
+
FLOWS: (userId) => `/${userId}/welcome_message_flows`
|
|
81
|
+
};
|
|
82
|
+
var MESSENGER_PROFILE_ENDPOINTS = {
|
|
83
|
+
/** Get/Set/Delete profile: /{user-id}/messenger_profile */
|
|
84
|
+
PROFILE: (userId) => `/${userId}/messenger_profile`
|
|
85
|
+
};
|
|
86
|
+
var COMMENT_ENDPOINTS = {
|
|
87
|
+
/** Get comment: GET /{comment-id} */
|
|
88
|
+
GET: (commentId) => `/${commentId}`,
|
|
89
|
+
/** Get replies: GET /{comment-id}/replies */
|
|
90
|
+
REPLIES: (commentId) => `/${commentId}/replies`,
|
|
91
|
+
/** Reply to comment: POST /{comment-id}/replies */
|
|
92
|
+
REPLY: (commentId) => `/${commentId}/replies`,
|
|
93
|
+
/** Hide/Unhide comment: POST /{comment-id} */
|
|
94
|
+
UPDATE: (commentId) => `/${commentId}`,
|
|
95
|
+
/** Delete comment: DELETE /{comment-id} */
|
|
96
|
+
DELETE: (commentId) => `/${commentId}`
|
|
97
|
+
};
|
|
98
|
+
var HASHTAG_ENDPOINTS = {
|
|
99
|
+
/** Search hashtag: GET /ig_hashtag_search */
|
|
100
|
+
SEARCH: "/ig_hashtag_search",
|
|
101
|
+
/** Get hashtag: GET /{hashtag-id} */
|
|
102
|
+
GET: (hashtagId) => `/${hashtagId}`,
|
|
103
|
+
/** Get recent media: GET /{hashtag-id}/recent_media */
|
|
104
|
+
RECENT_MEDIA: (hashtagId) => `/${hashtagId}/recent_media`,
|
|
105
|
+
/** Get top media: GET /{hashtag-id}/top_media */
|
|
106
|
+
TOP_MEDIA: (hashtagId) => `/${hashtagId}/top_media`
|
|
107
|
+
};
|
|
108
|
+
var AUTH_ENDPOINTS = {
|
|
109
|
+
/** Get current user: GET /me */
|
|
110
|
+
ME: "/me",
|
|
111
|
+
/** Refresh token: GET /refresh_access_token */
|
|
112
|
+
REFRESH_TOKEN: "/refresh_access_token",
|
|
113
|
+
/** Exchange token: GET /access_token */
|
|
114
|
+
ACCESS_TOKEN: "/access_token"
|
|
115
|
+
};
|
|
116
|
+
var OAUTH_ENDPOINTS = {
|
|
117
|
+
/** Authorization URL - redirect users here to start OAuth flow */
|
|
118
|
+
AUTHORIZE: "https://www.instagram.com/oauth/authorize",
|
|
119
|
+
/** Short-lived token exchange: POST with form data */
|
|
120
|
+
TOKEN: "https://api.instagram.com/oauth/access_token",
|
|
121
|
+
/** Long-lived token exchange: GET with query params */
|
|
122
|
+
LONG_LIVED_TOKEN: "https://graph.instagram.com/access_token"
|
|
123
|
+
};
|
|
124
|
+
var OEMBED_ENDPOINTS = {
|
|
125
|
+
/** Get oEmbed: GET /instagram_oembed */
|
|
126
|
+
GET: "/instagram_oembed"
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/errors.ts
|
|
130
|
+
var InstagramAPIError = class _InstagramAPIError extends Error {
|
|
131
|
+
constructor(message, code, type, subcode, fbTraceId) {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = "InstagramAPIError";
|
|
134
|
+
this.code = code;
|
|
135
|
+
this.type = type;
|
|
136
|
+
this.subcode = subcode;
|
|
137
|
+
this.fbTraceId = fbTraceId;
|
|
138
|
+
if (Error.captureStackTrace) {
|
|
139
|
+
Error.captureStackTrace(this, _InstagramAPIError);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Create an InstagramAPIError from an API response
|
|
144
|
+
*/
|
|
145
|
+
static fromResponse(response) {
|
|
146
|
+
const { error } = response;
|
|
147
|
+
if (error.code === 190) {
|
|
148
|
+
return new AuthenticationError(error.message, error.code, error.fbtrace_id);
|
|
149
|
+
}
|
|
150
|
+
if (error.code === 4 || error.code === 17 || error.code === 32) {
|
|
151
|
+
return new RateLimitError(error.message, error.code, error.fbtrace_id);
|
|
152
|
+
}
|
|
153
|
+
if (error.code === 100) {
|
|
154
|
+
return new ValidationError(error.message, error.code, error.error_subcode, error.fbtrace_id);
|
|
155
|
+
}
|
|
156
|
+
return new _InstagramAPIError(
|
|
157
|
+
error.message,
|
|
158
|
+
error.code,
|
|
159
|
+
error.type,
|
|
160
|
+
error.error_subcode,
|
|
161
|
+
error.fbtrace_id
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var AuthenticationError = class extends InstagramAPIError {
|
|
166
|
+
constructor(message, code = 190, fbTraceId) {
|
|
167
|
+
super(message, code, "OAuthException", void 0, fbTraceId);
|
|
168
|
+
this.name = "AuthenticationError";
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var RateLimitError = class extends InstagramAPIError {
|
|
172
|
+
constructor(message, code = 4, fbTraceId, retryAfter) {
|
|
173
|
+
super(message, code, "RateLimitException", void 0, fbTraceId);
|
|
174
|
+
this.name = "RateLimitError";
|
|
175
|
+
this.retryAfter = retryAfter;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
var ValidationError = class extends InstagramAPIError {
|
|
179
|
+
constructor(message, code = 100, subcode, fbTraceId) {
|
|
180
|
+
super(message, code, "ValidationException", subcode, fbTraceId);
|
|
181
|
+
this.name = "ValidationError";
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var NetworkError = class _NetworkError extends Error {
|
|
185
|
+
constructor(message, originalError) {
|
|
186
|
+
super(message);
|
|
187
|
+
this.name = "NetworkError";
|
|
188
|
+
this.originalError = originalError;
|
|
189
|
+
if (Error.captureStackTrace) {
|
|
190
|
+
Error.captureStackTrace(this, _NetworkError);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
function isInstagramAPIError(error) {
|
|
195
|
+
return error instanceof InstagramAPIError;
|
|
196
|
+
}
|
|
197
|
+
function isAuthenticationError(error) {
|
|
198
|
+
return error instanceof AuthenticationError;
|
|
199
|
+
}
|
|
200
|
+
function isRateLimitError(error) {
|
|
201
|
+
return error instanceof RateLimitError;
|
|
202
|
+
}
|
|
203
|
+
function isValidationError(error) {
|
|
204
|
+
return error instanceof ValidationError;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/http.ts
|
|
208
|
+
var HttpClient = class {
|
|
209
|
+
constructor(config) {
|
|
210
|
+
this.config = config;
|
|
211
|
+
this.client = axios__default.default.create({
|
|
212
|
+
baseURL: `${INSTAGRAM_BASE_URL}/${config.apiVersion}`,
|
|
213
|
+
timeout: config.timeout ?? 3e4,
|
|
214
|
+
headers: {
|
|
215
|
+
"Content-Type": "application/json",
|
|
216
|
+
...config.headers
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
this.setupInterceptors();
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Set up request and response interceptors
|
|
223
|
+
*/
|
|
224
|
+
setupInterceptors() {
|
|
225
|
+
this.client.interceptors.request.use(
|
|
226
|
+
(config) => {
|
|
227
|
+
if (config.params) {
|
|
228
|
+
config.params.access_token = this.config.accessToken;
|
|
229
|
+
} else {
|
|
230
|
+
config.params = { access_token: this.config.accessToken };
|
|
231
|
+
}
|
|
232
|
+
return config;
|
|
233
|
+
},
|
|
234
|
+
(error) => Promise.reject(error)
|
|
235
|
+
);
|
|
236
|
+
this.client.interceptors.response.use(
|
|
237
|
+
(response) => response,
|
|
238
|
+
(error) => {
|
|
239
|
+
if (!error.response) {
|
|
240
|
+
throw new NetworkError(
|
|
241
|
+
error.message || "Network error occurred",
|
|
242
|
+
error
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
const response = error.response;
|
|
246
|
+
if (response.data?.error) {
|
|
247
|
+
throw InstagramAPIError.fromResponse(response.data);
|
|
248
|
+
}
|
|
249
|
+
if (response.status === 429) {
|
|
250
|
+
const retryAfter = parseInt(response.headers["retry-after"] || "60", 10);
|
|
251
|
+
throw new RateLimitError(
|
|
252
|
+
"Rate limit exceeded",
|
|
253
|
+
4,
|
|
254
|
+
response.headers["x-fb-trace-id"],
|
|
255
|
+
retryAfter
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
throw new InstagramAPIError(
|
|
259
|
+
response.data?.message || "Unknown error occurred",
|
|
260
|
+
response.status,
|
|
261
|
+
"UnknownError"
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Update access token
|
|
268
|
+
*/
|
|
269
|
+
setAccessToken(accessToken) {
|
|
270
|
+
this.config.accessToken = accessToken;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* GET request
|
|
274
|
+
*/
|
|
275
|
+
async get(path, params, config) {
|
|
276
|
+
const response = await this.client.get(path, {
|
|
277
|
+
...config,
|
|
278
|
+
params: { ...params }
|
|
279
|
+
});
|
|
280
|
+
return response.data;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* POST request
|
|
284
|
+
*/
|
|
285
|
+
async post(path, data, params, config) {
|
|
286
|
+
const response = await this.client.post(path, data, {
|
|
287
|
+
...config,
|
|
288
|
+
params: { ...params }
|
|
289
|
+
});
|
|
290
|
+
return response.data;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* DELETE request
|
|
294
|
+
*/
|
|
295
|
+
async delete(path, params, config) {
|
|
296
|
+
const response = await this.client.delete(path, {
|
|
297
|
+
...config,
|
|
298
|
+
params: { ...params }
|
|
299
|
+
});
|
|
300
|
+
return response.data;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* POST with form data (for file uploads)
|
|
304
|
+
*/
|
|
305
|
+
async postForm(path, data, config) {
|
|
306
|
+
const formData = new URLSearchParams();
|
|
307
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
308
|
+
if (value !== void 0 && value !== null) {
|
|
309
|
+
formData.append(key, String(value));
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
const response = await this.client.post(path, formData, {
|
|
313
|
+
...config,
|
|
314
|
+
headers: {
|
|
315
|
+
...config?.headers,
|
|
316
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
return response.data;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// src/api/auth.ts
|
|
324
|
+
var AuthApi = class {
|
|
325
|
+
constructor(http) {
|
|
326
|
+
this.http = http;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get current user information
|
|
330
|
+
* @param fields - Fields to retrieve
|
|
331
|
+
*/
|
|
332
|
+
async me(fields = "id,username") {
|
|
333
|
+
return this.http.get(AUTH_ENDPOINTS.ME, { fields });
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Refresh a long-lived token (Instagram Login)
|
|
337
|
+
* Returns a new long-lived token with 60 days expiry
|
|
338
|
+
*/
|
|
339
|
+
async refreshToken() {
|
|
340
|
+
return this.http.get(
|
|
341
|
+
AUTH_ENDPOINTS.REFRESH_TOKEN,
|
|
342
|
+
{ grant_type: "ig_refresh_token" }
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Exchange short-lived token for long-lived token (Instagram Login)
|
|
347
|
+
* @param shortLivedToken - Short-lived access token (1 hour expiry)
|
|
348
|
+
* @param appSecret - Instagram App Secret
|
|
349
|
+
*/
|
|
350
|
+
async exchangeToken(shortLivedToken, appSecret) {
|
|
351
|
+
return this.http.get(
|
|
352
|
+
AUTH_ENDPOINTS.ACCESS_TOKEN,
|
|
353
|
+
{
|
|
354
|
+
grant_type: "ig_exchange_token",
|
|
355
|
+
client_secret: appSecret,
|
|
356
|
+
access_token: shortLivedToken
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// src/api/oauth.ts
|
|
363
|
+
var InstagramOAuth = class {
|
|
364
|
+
/**
|
|
365
|
+
* Build the Instagram authorization URL
|
|
366
|
+
*
|
|
367
|
+
* Direct users to this URL to start the OAuth flow.
|
|
368
|
+
* They will be asked to grant permissions to your app.
|
|
369
|
+
*
|
|
370
|
+
* @param params - Authorization URL parameters
|
|
371
|
+
* @returns Full authorization URL to redirect user to
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* const url = InstagramOAuth.buildAuthorizationUrl({
|
|
376
|
+
* clientId: process.env.INSTAGRAM_APP_ID,
|
|
377
|
+
* redirectUri: `${process.env.APP_URL}/api/instagram/callback`,
|
|
378
|
+
* scopes: [
|
|
379
|
+
* 'instagram_business_basic',
|
|
380
|
+
* 'instagram_business_manage_messages',
|
|
381
|
+
* ],
|
|
382
|
+
* state: crypto.randomUUID(), // CSRF protection
|
|
383
|
+
* });
|
|
384
|
+
*
|
|
385
|
+
* // Redirect user to url
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
static buildAuthorizationUrl(params) {
|
|
389
|
+
const {
|
|
390
|
+
clientId,
|
|
391
|
+
redirectUri,
|
|
392
|
+
scopes,
|
|
393
|
+
state,
|
|
394
|
+
responseType = "code",
|
|
395
|
+
forceReauth
|
|
396
|
+
} = params;
|
|
397
|
+
const queryParams = new URLSearchParams({
|
|
398
|
+
client_id: clientId,
|
|
399
|
+
redirect_uri: redirectUri,
|
|
400
|
+
scope: scopes.join(","),
|
|
401
|
+
response_type: responseType
|
|
402
|
+
});
|
|
403
|
+
if (state) {
|
|
404
|
+
queryParams.set("state", state);
|
|
405
|
+
}
|
|
406
|
+
if (forceReauth) {
|
|
407
|
+
queryParams.set("force_reauth", "true");
|
|
408
|
+
}
|
|
409
|
+
return `${OAUTH_ENDPOINTS.AUTHORIZE}?${queryParams.toString()}`;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Parse OAuth callback parameters from URL
|
|
413
|
+
*
|
|
414
|
+
* Use this to extract code, state, and error info from the callback URL.
|
|
415
|
+
*
|
|
416
|
+
* @param url - The callback URL (or just the search params)
|
|
417
|
+
* @returns Parsed callback parameters
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```typescript
|
|
421
|
+
* const params = InstagramOAuth.parseCallback(request.url);
|
|
422
|
+
*
|
|
423
|
+
* if (params.error) {
|
|
424
|
+
* // User denied access
|
|
425
|
+
* console.log(params.error_description);
|
|
426
|
+
* } else {
|
|
427
|
+
* // Exchange code for token
|
|
428
|
+
* const tokens = await InstagramOAuth.exchangeCodeForToken({
|
|
429
|
+
* code: params.code!,
|
|
430
|
+
* ...
|
|
431
|
+
* });
|
|
432
|
+
* }
|
|
433
|
+
* ```
|
|
434
|
+
*/
|
|
435
|
+
static parseCallback(url) {
|
|
436
|
+
const urlObj = new URL(url, "https://placeholder.com");
|
|
437
|
+
const params = urlObj.searchParams;
|
|
438
|
+
let code = params.get("code") || void 0;
|
|
439
|
+
if (code) {
|
|
440
|
+
code = code.replace(/#_$/, "");
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
code,
|
|
444
|
+
state: params.get("state") || void 0,
|
|
445
|
+
error: params.get("error") || void 0,
|
|
446
|
+
error_reason: params.get("error_reason") || void 0,
|
|
447
|
+
error_description: params.get("error_description") || void 0
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Exchange authorization code for tokens
|
|
452
|
+
*
|
|
453
|
+
* This is the recommended method - it handles the full flow:
|
|
454
|
+
* 1. Exchange code for short-lived token (1 hour)
|
|
455
|
+
* 2. Exchange short-lived for long-lived token (60 days)
|
|
456
|
+
*
|
|
457
|
+
* @param params - Exchange parameters
|
|
458
|
+
* @returns Long-lived token response with user ID
|
|
459
|
+
* @throws Error if exchange fails
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```typescript
|
|
463
|
+
* const tokens = await InstagramOAuth.exchangeCodeForToken({
|
|
464
|
+
* clientId: process.env.INSTAGRAM_APP_ID!,
|
|
465
|
+
* clientSecret: process.env.INSTAGRAM_APP_SECRET!,
|
|
466
|
+
* code: codeFromCallback,
|
|
467
|
+
* redirectUri: `${process.env.APP_URL}/api/instagram/callback`,
|
|
468
|
+
* });
|
|
469
|
+
*
|
|
470
|
+
* // Store tokens.access_token and tokens.user_id in your database
|
|
471
|
+
* // Token expires in tokens.expires_in seconds (~60 days)
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
static async exchangeCodeForToken(params) {
|
|
475
|
+
const shortLived = await this.getShortLivedToken(params);
|
|
476
|
+
const longLived = await this.getLongLivedToken({
|
|
477
|
+
clientSecret: params.clientSecret,
|
|
478
|
+
accessToken: shortLived.access_token
|
|
479
|
+
});
|
|
480
|
+
return {
|
|
481
|
+
access_token: longLived.access_token,
|
|
482
|
+
token_type: longLived.token_type,
|
|
483
|
+
expires_in: longLived.expires_in,
|
|
484
|
+
user_id: shortLived.user_id
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Get short-lived token from authorization code
|
|
489
|
+
*
|
|
490
|
+
* Use this if you need more control over the token exchange process.
|
|
491
|
+
* The short-lived token is valid for 1 hour.
|
|
492
|
+
*
|
|
493
|
+
* @param params - Exchange parameters
|
|
494
|
+
* @returns Short-lived token response
|
|
495
|
+
* @throws Error if exchange fails
|
|
496
|
+
*/
|
|
497
|
+
static async getShortLivedToken(params) {
|
|
498
|
+
const { clientId, clientSecret, code, redirectUri } = params;
|
|
499
|
+
const cleanCode = code.replace(/#_$/, "");
|
|
500
|
+
const formData = new FormData();
|
|
501
|
+
formData.append("client_id", clientId);
|
|
502
|
+
formData.append("client_secret", clientSecret);
|
|
503
|
+
formData.append("grant_type", "authorization_code");
|
|
504
|
+
formData.append("redirect_uri", redirectUri);
|
|
505
|
+
formData.append("code", cleanCode);
|
|
506
|
+
const response = await fetch(OAUTH_ENDPOINTS.TOKEN, {
|
|
507
|
+
method: "POST",
|
|
508
|
+
body: formData
|
|
509
|
+
});
|
|
510
|
+
const responseText = await response.text();
|
|
511
|
+
if (!response.ok) {
|
|
512
|
+
let error;
|
|
513
|
+
try {
|
|
514
|
+
error = JSON.parse(responseText);
|
|
515
|
+
} catch {
|
|
516
|
+
error = { error_message: responseText };
|
|
517
|
+
}
|
|
518
|
+
throw new Error(
|
|
519
|
+
error.error_message || error.error?.message || "Failed to exchange authorization code for token"
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
const data = JSON.parse(responseText);
|
|
523
|
+
if (data.data && Array.isArray(data.data) && data.data.length > 0) {
|
|
524
|
+
return {
|
|
525
|
+
access_token: data.data[0].access_token,
|
|
526
|
+
user_id: data.data[0].user_id,
|
|
527
|
+
permissions: data.data[0].permissions || ""
|
|
528
|
+
};
|
|
529
|
+
} else if (data.access_token) {
|
|
530
|
+
return {
|
|
531
|
+
access_token: data.access_token,
|
|
532
|
+
user_id: data.user_id,
|
|
533
|
+
permissions: data.permissions || ""
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
throw new Error("Unexpected token response format");
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Exchange short-lived token for long-lived token
|
|
540
|
+
*
|
|
541
|
+
* Long-lived tokens are valid for 60 days and can be refreshed.
|
|
542
|
+
*
|
|
543
|
+
* @param params - Exchange parameters
|
|
544
|
+
* @returns Long-lived token response
|
|
545
|
+
* @throws Error if exchange fails
|
|
546
|
+
*/
|
|
547
|
+
static async getLongLivedToken(params) {
|
|
548
|
+
const { clientSecret, accessToken } = params;
|
|
549
|
+
const queryParams = new URLSearchParams({
|
|
550
|
+
grant_type: "ig_exchange_token",
|
|
551
|
+
client_secret: clientSecret,
|
|
552
|
+
access_token: accessToken
|
|
553
|
+
});
|
|
554
|
+
const response = await fetch(
|
|
555
|
+
`${OAUTH_ENDPOINTS.LONG_LIVED_TOKEN}?${queryParams.toString()}`
|
|
556
|
+
);
|
|
557
|
+
const responseText = await response.text();
|
|
558
|
+
if (!response.ok) {
|
|
559
|
+
let error;
|
|
560
|
+
try {
|
|
561
|
+
error = JSON.parse(responseText);
|
|
562
|
+
} catch {
|
|
563
|
+
error = { error: { message: responseText } };
|
|
564
|
+
}
|
|
565
|
+
throw new Error(
|
|
566
|
+
error.error?.message || "Failed to exchange for long-lived token"
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
return JSON.parse(responseText);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Get the default scopes for Instagram Business Login
|
|
573
|
+
*
|
|
574
|
+
* @returns Array of commonly used scopes
|
|
575
|
+
*/
|
|
576
|
+
static getDefaultScopes() {
|
|
577
|
+
return [
|
|
578
|
+
"instagram_business_basic",
|
|
579
|
+
"instagram_business_manage_messages",
|
|
580
|
+
"instagram_business_manage_comments",
|
|
581
|
+
"instagram_business_content_publish"
|
|
582
|
+
];
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Get all available Instagram Business Login scopes
|
|
586
|
+
*
|
|
587
|
+
* @returns Array of all available scopes
|
|
588
|
+
*/
|
|
589
|
+
static getAllScopes() {
|
|
590
|
+
return [
|
|
591
|
+
"instagram_business_basic",
|
|
592
|
+
"instagram_business_manage_messages",
|
|
593
|
+
"instagram_business_manage_comments",
|
|
594
|
+
"instagram_business_content_publish",
|
|
595
|
+
"instagram_business_manage_insights"
|
|
596
|
+
];
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// src/types/common.ts
|
|
601
|
+
function formatFields(fields) {
|
|
602
|
+
if (!fields) return void 0;
|
|
603
|
+
if (Array.isArray(fields)) return fields.join(",");
|
|
604
|
+
return fields;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/api/users.ts
|
|
608
|
+
var UsersApi = class {
|
|
609
|
+
constructor(http, userId) {
|
|
610
|
+
this.http = http;
|
|
611
|
+
this.userId = userId;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Get user profile information
|
|
615
|
+
* @param options - Fields to retrieve
|
|
616
|
+
*/
|
|
617
|
+
async getProfile(options) {
|
|
618
|
+
const fields = options?.fields?.join(",") || "id,username,account_type";
|
|
619
|
+
return this.http.get(USER_ENDPOINTS.PROFILE(this.userId), { fields });
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get user's media
|
|
623
|
+
* @param options - Pagination and fields options
|
|
624
|
+
*/
|
|
625
|
+
async getMedia(options) {
|
|
626
|
+
return this.http.get(
|
|
627
|
+
USER_ENDPOINTS.MEDIA(this.userId),
|
|
628
|
+
{
|
|
629
|
+
fields: formatFields(options?.fields) || "id,caption,media_type,media_url,permalink,timestamp",
|
|
630
|
+
limit: options?.limit,
|
|
631
|
+
after: options?.after,
|
|
632
|
+
before: options?.before
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Get user's stories
|
|
638
|
+
*/
|
|
639
|
+
async getStories() {
|
|
640
|
+
return this.http.get(
|
|
641
|
+
USER_ENDPOINTS.STORIES(this.userId),
|
|
642
|
+
{ fields: "id,media_type,media_url,timestamp" }
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Get user's live media
|
|
647
|
+
*/
|
|
648
|
+
async getLiveMedia() {
|
|
649
|
+
return this.http.get(
|
|
650
|
+
USER_ENDPOINTS.LIVE_MEDIA(this.userId),
|
|
651
|
+
{ fields: "id,media_type,timestamp" }
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Get content publishing limit (quota usage)
|
|
656
|
+
*/
|
|
657
|
+
async getContentPublishingLimit() {
|
|
658
|
+
return this.http.get(
|
|
659
|
+
USER_ENDPOINTS.CONTENT_PUBLISHING_LIMIT(this.userId),
|
|
660
|
+
{ fields: "quota_usage,config" }
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Discover another business account by username
|
|
665
|
+
* @param options - Username and fields to retrieve
|
|
666
|
+
*/
|
|
667
|
+
async getBusinessDiscovery(options) {
|
|
668
|
+
const fields = options.fields?.join(",") || "id,username,followers_count,media_count";
|
|
669
|
+
return this.http.get(
|
|
670
|
+
USER_ENDPOINTS.BUSINESS_DISCOVERY(this.userId),
|
|
671
|
+
{
|
|
672
|
+
fields: `business_discovery.username(${options.username}){${fields}}`
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Get media where user is mentioned
|
|
678
|
+
*/
|
|
679
|
+
async getMentionedMedia() {
|
|
680
|
+
return this.http.get(
|
|
681
|
+
USER_ENDPOINTS.MENTIONED_MEDIA(this.userId),
|
|
682
|
+
{ fields: "id,caption,media_type,timestamp" }
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Get comments where user is mentioned
|
|
687
|
+
*/
|
|
688
|
+
async getMentionedComment() {
|
|
689
|
+
return this.http.get(
|
|
690
|
+
USER_ENDPOINTS.MENTIONED_COMMENT(this.userId),
|
|
691
|
+
{ fields: "id,text,timestamp" }
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Get media user is tagged in
|
|
696
|
+
*/
|
|
697
|
+
async getTags() {
|
|
698
|
+
return this.http.get(
|
|
699
|
+
USER_ENDPOINTS.TAGS(this.userId),
|
|
700
|
+
{ fields: "id,media_type,timestamp" }
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Get recently searched hashtags
|
|
705
|
+
*/
|
|
706
|
+
async getRecentlySearchedHashtags() {
|
|
707
|
+
return this.http.get(
|
|
708
|
+
USER_ENDPOINTS.RECENTLY_SEARCHED_HASHTAGS(this.userId)
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Get available product catalogs
|
|
713
|
+
*/
|
|
714
|
+
async getAvailableCatalogs() {
|
|
715
|
+
return this.http.get(
|
|
716
|
+
USER_ENDPOINTS.AVAILABLE_CATALOGS(this.userId)
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Search products in a catalog
|
|
721
|
+
* @param catalogId - Catalog ID to search in
|
|
722
|
+
* @param query - Search query
|
|
723
|
+
*/
|
|
724
|
+
async searchCatalogProducts(catalogId, query) {
|
|
725
|
+
return this.http.get(
|
|
726
|
+
USER_ENDPOINTS.CATALOG_PRODUCT_SEARCH(this.userId),
|
|
727
|
+
{ catalog_id: catalogId, q: query }
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// src/api/media.ts
|
|
733
|
+
var MediaApi = class {
|
|
734
|
+
constructor(http) {
|
|
735
|
+
this.http = http;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Get media by ID
|
|
739
|
+
* @param mediaId - Media ID
|
|
740
|
+
* @param options - Fields to retrieve
|
|
741
|
+
*/
|
|
742
|
+
async get(mediaId, options) {
|
|
743
|
+
const fields = options?.fields?.join(",") || "id,caption,media_type,media_url,permalink,timestamp";
|
|
744
|
+
return this.http.get(MEDIA_ENDPOINTS.GET(mediaId), { fields });
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Get carousel children
|
|
748
|
+
* @param mediaId - Carousel media ID
|
|
749
|
+
* @param options - Fields to retrieve
|
|
750
|
+
*/
|
|
751
|
+
async getChildren(mediaId, options) {
|
|
752
|
+
const fields = formatFields(options?.fields) || "id,media_type,media_url";
|
|
753
|
+
return this.http.get(
|
|
754
|
+
MEDIA_ENDPOINTS.CHILDREN(mediaId),
|
|
755
|
+
{ fields }
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Get comments on media
|
|
760
|
+
* @param mediaId - Media ID
|
|
761
|
+
* @param options - Pagination and fields options
|
|
762
|
+
*/
|
|
763
|
+
async getComments(mediaId, options) {
|
|
764
|
+
return this.http.get(
|
|
765
|
+
MEDIA_ENDPOINTS.COMMENTS(mediaId),
|
|
766
|
+
{
|
|
767
|
+
fields: formatFields(options?.fields) || "id,text,username,timestamp",
|
|
768
|
+
limit: options?.limit,
|
|
769
|
+
after: options?.after
|
|
770
|
+
}
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get media insights
|
|
775
|
+
* @param mediaId - Media ID
|
|
776
|
+
* @param options - Metrics to retrieve
|
|
777
|
+
*/
|
|
778
|
+
async getInsights(mediaId, options) {
|
|
779
|
+
return this.http.get(
|
|
780
|
+
MEDIA_ENDPOINTS.INSIGHTS(mediaId),
|
|
781
|
+
{ metric: options.metric.join(",") }
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Get media collaborators
|
|
786
|
+
* @param mediaId - Media ID
|
|
787
|
+
*/
|
|
788
|
+
async getCollaborators(mediaId) {
|
|
789
|
+
return this.http.get(
|
|
790
|
+
MEDIA_ENDPOINTS.COLLABORATORS(mediaId),
|
|
791
|
+
{ fields: "id,username" }
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Get product tags on media
|
|
796
|
+
* @param mediaId - Media ID
|
|
797
|
+
*/
|
|
798
|
+
async getProductTags(mediaId) {
|
|
799
|
+
return this.http.get(
|
|
800
|
+
MEDIA_ENDPOINTS.PRODUCT_TAGS(mediaId)
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
// src/api/publishing.ts
|
|
806
|
+
var PublishingApi = class {
|
|
807
|
+
constructor(http, userId) {
|
|
808
|
+
this.http = http;
|
|
809
|
+
this.userId = userId;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Create an image container
|
|
813
|
+
* @param options - Image URL and optional caption, location, tags
|
|
814
|
+
*/
|
|
815
|
+
async createImageContainer(options) {
|
|
816
|
+
return this.http.post(
|
|
817
|
+
PUBLISHING_ENDPOINTS.CREATE_CONTAINER(this.userId),
|
|
818
|
+
{
|
|
819
|
+
image_url: options.image_url,
|
|
820
|
+
caption: options.caption,
|
|
821
|
+
location_id: options.location_id,
|
|
822
|
+
user_tags: options.user_tags ? JSON.stringify(options.user_tags) : void 0,
|
|
823
|
+
collaborators: options.collaborators?.join(","),
|
|
824
|
+
alt_text: options.alt_text,
|
|
825
|
+
is_carousel_item: options.is_carousel_item
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Create a video/reel/story container
|
|
831
|
+
* @param options - Video URL, media type, and optional settings
|
|
832
|
+
*/
|
|
833
|
+
async createVideoContainer(options) {
|
|
834
|
+
return this.http.post(
|
|
835
|
+
PUBLISHING_ENDPOINTS.CREATE_CONTAINER(this.userId),
|
|
836
|
+
{
|
|
837
|
+
video_url: options.video_url,
|
|
838
|
+
media_type: options.media_type,
|
|
839
|
+
caption: options.caption,
|
|
840
|
+
location_id: options.location_id,
|
|
841
|
+
user_tags: options.user_tags ? JSON.stringify(options.user_tags) : void 0,
|
|
842
|
+
collaborators: options.collaborators?.join(","),
|
|
843
|
+
share_to_feed: options.share_to_feed,
|
|
844
|
+
is_carousel_item: options.is_carousel_item,
|
|
845
|
+
cover_url: options.cover_url,
|
|
846
|
+
thumb_offset: options.thumb_offset,
|
|
847
|
+
audio_name: options.audio_name
|
|
848
|
+
}
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Create a carousel container
|
|
853
|
+
* @param options - Child container IDs and optional caption
|
|
854
|
+
*/
|
|
855
|
+
async createCarouselContainer(options) {
|
|
856
|
+
return this.http.post(
|
|
857
|
+
PUBLISHING_ENDPOINTS.CREATE_CONTAINER(this.userId),
|
|
858
|
+
{
|
|
859
|
+
media_type: "CAROUSEL",
|
|
860
|
+
children: options.children.join(","),
|
|
861
|
+
caption: options.caption,
|
|
862
|
+
location_id: options.location_id,
|
|
863
|
+
collaborators: options.collaborators?.join(",")
|
|
864
|
+
}
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Create a resumable upload session
|
|
869
|
+
* @param options - Media type and settings
|
|
870
|
+
*/
|
|
871
|
+
async createResumableUpload(options) {
|
|
872
|
+
return this.http.post(
|
|
873
|
+
PUBLISHING_ENDPOINTS.CREATE_CONTAINER(this.userId),
|
|
874
|
+
{
|
|
875
|
+
media_type: options.media_type,
|
|
876
|
+
upload_type: "resumable",
|
|
877
|
+
caption: options.caption,
|
|
878
|
+
location_id: options.location_id,
|
|
879
|
+
collaborators: options.collaborators?.join(",")
|
|
880
|
+
}
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Get container status
|
|
885
|
+
* @param containerId - Container ID
|
|
886
|
+
*/
|
|
887
|
+
async getContainerStatus(containerId) {
|
|
888
|
+
return this.http.get(
|
|
889
|
+
PUBLISHING_ENDPOINTS.CONTAINER_STATUS(containerId),
|
|
890
|
+
{ fields: "id,status_code,status" }
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Publish a container
|
|
895
|
+
* @param containerId - Container ID to publish
|
|
896
|
+
*/
|
|
897
|
+
async publishContainer(containerId) {
|
|
898
|
+
return this.http.post(
|
|
899
|
+
PUBLISHING_ENDPOINTS.PUBLISH(this.userId),
|
|
900
|
+
{ creation_id: containerId }
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Wait for container to be ready, then publish
|
|
905
|
+
* @param containerId - Container ID
|
|
906
|
+
* @param maxAttempts - Maximum number of status checks (default: 30)
|
|
907
|
+
* @param intervalMs - Interval between checks in ms (default: 2000)
|
|
908
|
+
*/
|
|
909
|
+
async waitAndPublish(containerId, maxAttempts = 30, intervalMs = 2e3) {
|
|
910
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
911
|
+
const status = await this.getContainerStatus(containerId);
|
|
912
|
+
if (status.status_code === "FINISHED") {
|
|
913
|
+
return this.publishContainer(containerId);
|
|
914
|
+
}
|
|
915
|
+
if (status.status_code === "ERROR" || status.status_code === "EXPIRED") {
|
|
916
|
+
throw new Error(`Container failed with status: ${status.status_code}`);
|
|
917
|
+
}
|
|
918
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
919
|
+
}
|
|
920
|
+
throw new Error("Container did not become ready in time");
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
// src/api/messaging.ts
|
|
925
|
+
var MessagingApi = class {
|
|
926
|
+
constructor(http, userId) {
|
|
927
|
+
this.http = http;
|
|
928
|
+
this.userId = userId;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Send a text message
|
|
932
|
+
* @param recipientId - Instagram-scoped user ID (IGSID)
|
|
933
|
+
* @param text - Message text
|
|
934
|
+
* @param options - Optional settings (humanAgent for 7-day window)
|
|
935
|
+
*/
|
|
936
|
+
async sendText(recipientId, text, options) {
|
|
937
|
+
const payload = {
|
|
938
|
+
recipient: { id: recipientId },
|
|
939
|
+
message: { text }
|
|
940
|
+
};
|
|
941
|
+
if (options?.humanAgent) {
|
|
942
|
+
payload.messaging_type = "MESSAGE_TAG";
|
|
943
|
+
payload.tag = "HUMAN_AGENT";
|
|
944
|
+
}
|
|
945
|
+
return this.http.post(
|
|
946
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
947
|
+
payload
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Send a media message (image, video, audio)
|
|
952
|
+
* @param recipientId - Instagram-scoped user ID
|
|
953
|
+
* @param options - Media type and URL
|
|
954
|
+
*/
|
|
955
|
+
async sendMedia(recipientId, options) {
|
|
956
|
+
return this.http.post(
|
|
957
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
958
|
+
{
|
|
959
|
+
recipient: { id: recipientId },
|
|
960
|
+
message: {
|
|
961
|
+
attachment: {
|
|
962
|
+
type: options.type,
|
|
963
|
+
payload: { url: options.url }
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Send a sticker (like_heart)
|
|
971
|
+
* @param recipientId - Instagram-scoped user ID
|
|
972
|
+
*/
|
|
973
|
+
async sendLikeHeart(recipientId) {
|
|
974
|
+
return this.http.post(
|
|
975
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
976
|
+
{
|
|
977
|
+
recipient: { id: recipientId },
|
|
978
|
+
message: {
|
|
979
|
+
attachment: {
|
|
980
|
+
type: "like_heart"
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Send a generic template
|
|
988
|
+
* @param recipientId - Instagram-scoped user ID
|
|
989
|
+
* @param options - Template elements
|
|
990
|
+
*/
|
|
991
|
+
async sendGenericTemplate(recipientId, options) {
|
|
992
|
+
return this.http.post(
|
|
993
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
994
|
+
{
|
|
995
|
+
recipient: { id: recipientId },
|
|
996
|
+
message: {
|
|
997
|
+
attachment: {
|
|
998
|
+
type: "template",
|
|
999
|
+
payload: {
|
|
1000
|
+
template_type: "generic",
|
|
1001
|
+
elements: options.elements
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Send a button template
|
|
1010
|
+
* @param recipientId - Instagram-scoped user ID
|
|
1011
|
+
* @param options - Text and buttons
|
|
1012
|
+
*/
|
|
1013
|
+
async sendButtonTemplate(recipientId, options) {
|
|
1014
|
+
return this.http.post(
|
|
1015
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
1016
|
+
{
|
|
1017
|
+
recipient: { id: recipientId },
|
|
1018
|
+
message: {
|
|
1019
|
+
attachment: {
|
|
1020
|
+
type: "template",
|
|
1021
|
+
payload: {
|
|
1022
|
+
template_type: "button",
|
|
1023
|
+
text: options.text,
|
|
1024
|
+
buttons: options.buttons
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Send quick replies
|
|
1033
|
+
* @param recipientId - Instagram-scoped user ID
|
|
1034
|
+
* @param options - Text and quick reply options
|
|
1035
|
+
*/
|
|
1036
|
+
async sendQuickReplies(recipientId, options) {
|
|
1037
|
+
return this.http.post(
|
|
1038
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
1039
|
+
{
|
|
1040
|
+
recipient: { id: recipientId },
|
|
1041
|
+
message: {
|
|
1042
|
+
text: options.text,
|
|
1043
|
+
quick_replies: options.replies
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Send a private reply to a comment
|
|
1050
|
+
* @param commentId - Comment ID to reply to
|
|
1051
|
+
* @param message - Message text
|
|
1052
|
+
*/
|
|
1053
|
+
async sendPrivateReply(commentId, message) {
|
|
1054
|
+
return this.http.post(
|
|
1055
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
1056
|
+
{
|
|
1057
|
+
recipient: { comment_id: commentId },
|
|
1058
|
+
message: { text: message }
|
|
1059
|
+
}
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Share a published post via message
|
|
1064
|
+
* @param recipientId - Instagram-scoped user ID
|
|
1065
|
+
* @param mediaId - Media ID of the post to share
|
|
1066
|
+
*/
|
|
1067
|
+
async sendMediaShare(recipientId, mediaId) {
|
|
1068
|
+
return this.http.post(
|
|
1069
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
1070
|
+
{
|
|
1071
|
+
recipient: { id: recipientId },
|
|
1072
|
+
message: {
|
|
1073
|
+
attachment: {
|
|
1074
|
+
type: "MEDIA_SHARE",
|
|
1075
|
+
payload: { id: mediaId }
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* React to a message
|
|
1083
|
+
* @param recipientId - Instagram-scoped user ID
|
|
1084
|
+
* @param messageId - Message ID to react to
|
|
1085
|
+
* @param reaction - Reaction type
|
|
1086
|
+
*/
|
|
1087
|
+
async reactToMessage(recipientId, messageId, reaction) {
|
|
1088
|
+
return this.http.post(
|
|
1089
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
1090
|
+
{
|
|
1091
|
+
recipient: { id: recipientId },
|
|
1092
|
+
sender_action: "react",
|
|
1093
|
+
payload: {
|
|
1094
|
+
message_id: messageId,
|
|
1095
|
+
reaction
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
/**
|
|
1101
|
+
* Remove reaction from a message
|
|
1102
|
+
* @param recipientId - Instagram-scoped user ID
|
|
1103
|
+
* @param messageId - Message ID to unreact from
|
|
1104
|
+
*/
|
|
1105
|
+
async unreactToMessage(recipientId, messageId) {
|
|
1106
|
+
return this.http.post(
|
|
1107
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
1108
|
+
{
|
|
1109
|
+
recipient: { id: recipientId },
|
|
1110
|
+
sender_action: "unreact",
|
|
1111
|
+
payload: {
|
|
1112
|
+
message_id: messageId
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Send typing indicator
|
|
1119
|
+
* @param recipientId - Instagram-scoped user ID
|
|
1120
|
+
* @param typing - Whether to show typing (true) or stop (false)
|
|
1121
|
+
*/
|
|
1122
|
+
async sendTypingIndicator(recipientId, typing = true) {
|
|
1123
|
+
return this.http.post(
|
|
1124
|
+
MESSAGING_ENDPOINTS.SEND(this.userId),
|
|
1125
|
+
{
|
|
1126
|
+
recipient: { id: recipientId },
|
|
1127
|
+
sender_action: typing ? "typing_on" : "typing_off"
|
|
1128
|
+
}
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
|
|
1133
|
+
// src/api/conversations.ts
|
|
1134
|
+
var ConversationsApi = class {
|
|
1135
|
+
constructor(http, userId) {
|
|
1136
|
+
this.http = http;
|
|
1137
|
+
this.userId = userId;
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Get list of conversations
|
|
1141
|
+
* @param options - Pagination and filter options
|
|
1142
|
+
*/
|
|
1143
|
+
async list(options) {
|
|
1144
|
+
return this.http.get(
|
|
1145
|
+
MESSAGING_ENDPOINTS.CONVERSATIONS(this.userId),
|
|
1146
|
+
{
|
|
1147
|
+
platform: options?.platform || "instagram",
|
|
1148
|
+
user_id: options?.user_id,
|
|
1149
|
+
limit: options?.limit,
|
|
1150
|
+
after: options?.after,
|
|
1151
|
+
fields: "id,updated_time"
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Find conversation with a specific user
|
|
1157
|
+
* @param igsid - Instagram-scoped user ID
|
|
1158
|
+
*/
|
|
1159
|
+
async findByUser(igsid) {
|
|
1160
|
+
return this.http.get(
|
|
1161
|
+
MESSAGING_ENDPOINTS.CONVERSATIONS(this.userId),
|
|
1162
|
+
{
|
|
1163
|
+
platform: "instagram",
|
|
1164
|
+
user_id: igsid,
|
|
1165
|
+
fields: "id,updated_time"
|
|
1166
|
+
}
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Get messages in a conversation
|
|
1171
|
+
* @param conversationId - Conversation ID
|
|
1172
|
+
* @param fields - Fields to retrieve (default: from,to)
|
|
1173
|
+
*/
|
|
1174
|
+
async getMessages(conversationId, fields = "messages{id,created_time,from,to,message}") {
|
|
1175
|
+
return this.http.get(
|
|
1176
|
+
MESSAGING_ENDPOINTS.CONVERSATION(conversationId),
|
|
1177
|
+
{ fields }
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Get a specific message
|
|
1182
|
+
* @param messageId - Message ID
|
|
1183
|
+
* @param fields - Fields to retrieve
|
|
1184
|
+
*/
|
|
1185
|
+
async getMessage(messageId, fields = "id,created_time,from,to,message") {
|
|
1186
|
+
return this.http.get(
|
|
1187
|
+
MESSAGING_ENDPOINTS.MESSAGE(messageId),
|
|
1188
|
+
{ fields }
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
// src/api/comments.ts
|
|
1194
|
+
var CommentsApi = class {
|
|
1195
|
+
constructor(http) {
|
|
1196
|
+
this.http = http;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Get comment by ID
|
|
1200
|
+
* @param commentId - Comment ID
|
|
1201
|
+
* @param fields - Fields to retrieve
|
|
1202
|
+
*/
|
|
1203
|
+
async get(commentId, fields = ["id", "text", "username", "timestamp"]) {
|
|
1204
|
+
return this.http.get(
|
|
1205
|
+
COMMENT_ENDPOINTS.GET(commentId),
|
|
1206
|
+
{ fields: fields.join(",") }
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Get replies to a comment
|
|
1211
|
+
* @param commentId - Comment ID
|
|
1212
|
+
* @param options - Pagination and fields options
|
|
1213
|
+
*/
|
|
1214
|
+
async getReplies(commentId, options) {
|
|
1215
|
+
return this.http.get(
|
|
1216
|
+
COMMENT_ENDPOINTS.REPLIES(commentId),
|
|
1217
|
+
{
|
|
1218
|
+
fields: options?.fields?.join(",") || "id,text,username,timestamp",
|
|
1219
|
+
limit: options?.limit,
|
|
1220
|
+
after: options?.after
|
|
1221
|
+
}
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Reply to a comment
|
|
1226
|
+
* @param commentId - Comment ID to reply to
|
|
1227
|
+
* @param options - Reply message
|
|
1228
|
+
*/
|
|
1229
|
+
async reply(commentId, options) {
|
|
1230
|
+
return this.http.post(
|
|
1231
|
+
COMMENT_ENDPOINTS.REPLY(commentId),
|
|
1232
|
+
{ message: options.message }
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Hide a comment
|
|
1237
|
+
* @param commentId - Comment ID
|
|
1238
|
+
*/
|
|
1239
|
+
async hide(commentId) {
|
|
1240
|
+
return this.http.post(
|
|
1241
|
+
COMMENT_ENDPOINTS.UPDATE(commentId),
|
|
1242
|
+
{ hide: true }
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Unhide a comment
|
|
1247
|
+
* @param commentId - Comment ID
|
|
1248
|
+
*/
|
|
1249
|
+
async unhide(commentId) {
|
|
1250
|
+
return this.http.post(
|
|
1251
|
+
COMMENT_ENDPOINTS.UPDATE(commentId),
|
|
1252
|
+
{ hide: false }
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Delete a comment
|
|
1257
|
+
* @param commentId - Comment ID
|
|
1258
|
+
*/
|
|
1259
|
+
async delete(commentId) {
|
|
1260
|
+
return this.http.delete(COMMENT_ENDPOINTS.DELETE(commentId));
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
// src/api/hashtags.ts
|
|
1265
|
+
var HashtagsApi = class {
|
|
1266
|
+
constructor(http) {
|
|
1267
|
+
this.http = http;
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Search for a hashtag
|
|
1271
|
+
* @param options - User ID and search query
|
|
1272
|
+
*/
|
|
1273
|
+
async search(options) {
|
|
1274
|
+
return this.http.get(
|
|
1275
|
+
HASHTAG_ENDPOINTS.SEARCH,
|
|
1276
|
+
{
|
|
1277
|
+
user_id: options.user_id,
|
|
1278
|
+
q: options.q
|
|
1279
|
+
}
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Get hashtag information
|
|
1284
|
+
* @param hashtagId - Hashtag ID
|
|
1285
|
+
*/
|
|
1286
|
+
async get(hashtagId) {
|
|
1287
|
+
return this.http.get(
|
|
1288
|
+
HASHTAG_ENDPOINTS.GET(hashtagId),
|
|
1289
|
+
{ fields: "id,name" }
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Get recent media with hashtag
|
|
1294
|
+
* @param hashtagId - Hashtag ID
|
|
1295
|
+
* @param options - User ID and pagination options
|
|
1296
|
+
*/
|
|
1297
|
+
async getRecentMedia(hashtagId, options) {
|
|
1298
|
+
return this.http.get(
|
|
1299
|
+
HASHTAG_ENDPOINTS.RECENT_MEDIA(hashtagId),
|
|
1300
|
+
{
|
|
1301
|
+
user_id: options.user_id,
|
|
1302
|
+
fields: options.fields?.join(",") || "id,caption,media_type,permalink",
|
|
1303
|
+
limit: options.limit,
|
|
1304
|
+
after: options.after
|
|
1305
|
+
}
|
|
1306
|
+
);
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Get top media with hashtag
|
|
1310
|
+
* @param hashtagId - Hashtag ID
|
|
1311
|
+
* @param options - User ID and pagination options
|
|
1312
|
+
*/
|
|
1313
|
+
async getTopMedia(hashtagId, options) {
|
|
1314
|
+
return this.http.get(
|
|
1315
|
+
HASHTAG_ENDPOINTS.TOP_MEDIA(hashtagId),
|
|
1316
|
+
{
|
|
1317
|
+
user_id: options.user_id,
|
|
1318
|
+
fields: options.fields?.join(",") || "id,caption,media_type,permalink",
|
|
1319
|
+
limit: options.limit,
|
|
1320
|
+
after: options.after
|
|
1321
|
+
}
|
|
1322
|
+
);
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
// src/api/insights.ts
|
|
1327
|
+
var InsightsApi = class {
|
|
1328
|
+
constructor(http, userId) {
|
|
1329
|
+
this.http = http;
|
|
1330
|
+
this.userId = userId;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Get account insights
|
|
1334
|
+
* @param options - Metrics, period, and breakdown options
|
|
1335
|
+
*/
|
|
1336
|
+
async getAccountInsights(options) {
|
|
1337
|
+
return this.http.get(
|
|
1338
|
+
USER_ENDPOINTS.INSIGHTS(this.userId),
|
|
1339
|
+
{
|
|
1340
|
+
metric: options.metric.join(","),
|
|
1341
|
+
period: options.period || "day",
|
|
1342
|
+
since: options.since,
|
|
1343
|
+
until: options.until,
|
|
1344
|
+
metric_type: options.metric_type,
|
|
1345
|
+
breakdown: options.breakdown
|
|
1346
|
+
}
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Get media insights
|
|
1351
|
+
* @param mediaId - Media ID
|
|
1352
|
+
* @param options - Metrics to retrieve
|
|
1353
|
+
*/
|
|
1354
|
+
async getMediaInsights(mediaId, options) {
|
|
1355
|
+
return this.http.get(
|
|
1356
|
+
MEDIA_ENDPOINTS.INSIGHTS(mediaId),
|
|
1357
|
+
{ metric: options.metric.join(",") }
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
// src/api/welcomeFlows.ts
|
|
1363
|
+
var WelcomeFlowsApi = class {
|
|
1364
|
+
constructor(http, userId) {
|
|
1365
|
+
this.http = http;
|
|
1366
|
+
this.userId = userId;
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Get all welcome message flows
|
|
1370
|
+
*/
|
|
1371
|
+
async list() {
|
|
1372
|
+
return this.http.get(
|
|
1373
|
+
WELCOME_FLOW_ENDPOINTS.FLOWS(this.userId)
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Get a specific welcome message flow
|
|
1378
|
+
* @param flowId - Flow ID
|
|
1379
|
+
*/
|
|
1380
|
+
async get(flowId) {
|
|
1381
|
+
return this.http.get(
|
|
1382
|
+
WELCOME_FLOW_ENDPOINTS.FLOWS(this.userId),
|
|
1383
|
+
{ flow_id: flowId }
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Create a new welcome message flow
|
|
1388
|
+
* @param options - Flow name and screens
|
|
1389
|
+
*/
|
|
1390
|
+
async create(options) {
|
|
1391
|
+
return this.http.post(
|
|
1392
|
+
WELCOME_FLOW_ENDPOINTS.FLOWS(this.userId),
|
|
1393
|
+
{
|
|
1394
|
+
name: options.name,
|
|
1395
|
+
screens: options.screens
|
|
1396
|
+
}
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* Update an existing welcome message flow
|
|
1401
|
+
* @param flowId - Flow ID
|
|
1402
|
+
* @param options - Updated flow name and screens
|
|
1403
|
+
*/
|
|
1404
|
+
async update(flowId, options) {
|
|
1405
|
+
return this.http.post(
|
|
1406
|
+
WELCOME_FLOW_ENDPOINTS.FLOWS(this.userId),
|
|
1407
|
+
{
|
|
1408
|
+
flow_id: flowId,
|
|
1409
|
+
name: options.name,
|
|
1410
|
+
screens: options.screens
|
|
1411
|
+
}
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Delete a welcome message flow
|
|
1416
|
+
* @param flowId - Flow ID
|
|
1417
|
+
*/
|
|
1418
|
+
async delete(flowId) {
|
|
1419
|
+
return this.http.delete(
|
|
1420
|
+
WELCOME_FLOW_ENDPOINTS.FLOWS(this.userId),
|
|
1421
|
+
{ flow_id: flowId }
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
// src/api/messengerProfile.ts
|
|
1427
|
+
var MessengerProfileApi = class {
|
|
1428
|
+
constructor(http, userId) {
|
|
1429
|
+
this.http = http;
|
|
1430
|
+
this.userId = userId;
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Get current ice breakers
|
|
1434
|
+
*/
|
|
1435
|
+
async getIceBreakers() {
|
|
1436
|
+
return this.http.get(
|
|
1437
|
+
MESSENGER_PROFILE_ENDPOINTS.PROFILE(this.userId),
|
|
1438
|
+
{ fields: "ice_breakers" }
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Set ice breakers (FAQ questions)
|
|
1443
|
+
* @param iceBreakers - Ice breaker configurations (max 4 questions per locale)
|
|
1444
|
+
*/
|
|
1445
|
+
async setIceBreakers(iceBreakers) {
|
|
1446
|
+
return this.http.post(
|
|
1447
|
+
MESSENGER_PROFILE_ENDPOINTS.PROFILE(this.userId),
|
|
1448
|
+
{
|
|
1449
|
+
platform: "instagram",
|
|
1450
|
+
ice_breakers: iceBreakers
|
|
1451
|
+
}
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Delete ice breakers
|
|
1456
|
+
*/
|
|
1457
|
+
async deleteIceBreakers() {
|
|
1458
|
+
return this.http.delete(
|
|
1459
|
+
MESSENGER_PROFILE_ENDPOINTS.PROFILE(this.userId),
|
|
1460
|
+
{ fields: ["ice_breakers"] }
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Get persistent menu
|
|
1465
|
+
*/
|
|
1466
|
+
async getPersistentMenu() {
|
|
1467
|
+
return this.http.get(
|
|
1468
|
+
MESSENGER_PROFILE_ENDPOINTS.PROFILE(this.userId),
|
|
1469
|
+
{ fields: "persistent_menu" }
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Set persistent menu
|
|
1474
|
+
* @param menus - Persistent menu configurations
|
|
1475
|
+
*/
|
|
1476
|
+
async setPersistentMenu(menus) {
|
|
1477
|
+
return this.http.post(
|
|
1478
|
+
MESSENGER_PROFILE_ENDPOINTS.PROFILE(this.userId),
|
|
1479
|
+
{
|
|
1480
|
+
platform: "instagram",
|
|
1481
|
+
persistent_menu: menus
|
|
1482
|
+
}
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Delete persistent menu
|
|
1487
|
+
*/
|
|
1488
|
+
async deletePersistentMenu() {
|
|
1489
|
+
return this.http.delete(
|
|
1490
|
+
MESSENGER_PROFILE_ENDPOINTS.PROFILE(this.userId),
|
|
1491
|
+
{ fields: ["persistent_menu"] }
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
|
|
1496
|
+
// src/api/oembed.ts
|
|
1497
|
+
var OEmbedApi = class {
|
|
1498
|
+
constructor(http) {
|
|
1499
|
+
this.http = http;
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* Get oEmbed data for an Instagram URL
|
|
1503
|
+
* @param options - URL and optional formatting options
|
|
1504
|
+
*/
|
|
1505
|
+
async get(options) {
|
|
1506
|
+
return this.http.get(
|
|
1507
|
+
OEMBED_ENDPOINTS.GET,
|
|
1508
|
+
{
|
|
1509
|
+
url: options.url,
|
|
1510
|
+
maxwidth: options.maxwidth,
|
|
1511
|
+
hidecaption: options.hidecaption,
|
|
1512
|
+
omitscript: options.omitscript
|
|
1513
|
+
}
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
|
|
1518
|
+
// src/api/webhooks.ts
|
|
1519
|
+
var WebhooksApi = class {
|
|
1520
|
+
constructor(http, userId) {
|
|
1521
|
+
this.http = http;
|
|
1522
|
+
this.userId = userId;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Subscribe your app to specific fields for this user/page.
|
|
1526
|
+
*
|
|
1527
|
+
* @param fields List of fields to subscribe to (e.g., ['messages', 'comments'])
|
|
1528
|
+
*/
|
|
1529
|
+
async subscribe(fields) {
|
|
1530
|
+
return this.http.post(`/${this.userId}/subscribed_apps`, {
|
|
1531
|
+
subscribed_fields: fields.join(",")
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Unsubscribe from specific fields or all fields.
|
|
1536
|
+
*
|
|
1537
|
+
* @param fields Optional list of fields to unsubscribe from. If empty, unsubscribes from all.
|
|
1538
|
+
*/
|
|
1539
|
+
async unsubscribe(fields) {
|
|
1540
|
+
if (fields && fields.length > 0) {
|
|
1541
|
+
throw new Error("Partial unsubscribe not directly supported by API. Use subscribe() with the fields you want to keep.");
|
|
1542
|
+
}
|
|
1543
|
+
return this.http.delete(`/${this.userId}/subscribed_apps`);
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Get the list of fields your app is currently subscribed to.
|
|
1547
|
+
*/
|
|
1548
|
+
async getSubscribedFields() {
|
|
1549
|
+
return this.http.get(`/${this.userId}/subscribed_apps`);
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
// src/client.ts
|
|
1554
|
+
var DEFAULT_API_VERSION = "v22.0";
|
|
1555
|
+
var InstagramClient = class {
|
|
1556
|
+
/**
|
|
1557
|
+
* Create a new Instagram client
|
|
1558
|
+
* @param config - Client configuration
|
|
1559
|
+
*/
|
|
1560
|
+
constructor(config) {
|
|
1561
|
+
this.cachedUserId = null;
|
|
1562
|
+
this.config = {
|
|
1563
|
+
accessToken: config.accessToken,
|
|
1564
|
+
apiVersion: config.apiVersion || DEFAULT_API_VERSION,
|
|
1565
|
+
timeout: config.timeout || 3e4
|
|
1566
|
+
};
|
|
1567
|
+
this.http = new HttpClient({
|
|
1568
|
+
accessToken: this.config.accessToken,
|
|
1569
|
+
apiVersion: this.config.apiVersion,
|
|
1570
|
+
timeout: this.config.timeout
|
|
1571
|
+
});
|
|
1572
|
+
const userId = "me";
|
|
1573
|
+
this.auth = new AuthApi(this.http);
|
|
1574
|
+
this.users = new UsersApi(this.http, userId);
|
|
1575
|
+
this.media = new MediaApi(this.http);
|
|
1576
|
+
this.publishing = new PublishingApi(this.http, userId);
|
|
1577
|
+
this.messaging = new MessagingApi(this.http, userId);
|
|
1578
|
+
this.conversations = new ConversationsApi(this.http, userId);
|
|
1579
|
+
this.comments = new CommentsApi(this.http);
|
|
1580
|
+
this.hashtags = new HashtagsApi(this.http);
|
|
1581
|
+
this.insights = new InsightsApi(this.http, userId);
|
|
1582
|
+
this.welcomeFlows = new WelcomeFlowsApi(this.http, userId);
|
|
1583
|
+
this.messengerProfile = new MessengerProfileApi(this.http, userId);
|
|
1584
|
+
this.oembed = new OEmbedApi(this.http);
|
|
1585
|
+
this.webhooks = new WebhooksApi(this.http, userId);
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Get the current user ID
|
|
1589
|
+
* Fetches from API if not cached
|
|
1590
|
+
*/
|
|
1591
|
+
async getUserId() {
|
|
1592
|
+
if (this.cachedUserId) {
|
|
1593
|
+
return this.cachedUserId;
|
|
1594
|
+
}
|
|
1595
|
+
const user = await this.auth.me("id");
|
|
1596
|
+
this.cachedUserId = user.id;
|
|
1597
|
+
return user.id;
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Update the access token
|
|
1601
|
+
* @param accessToken - New access token
|
|
1602
|
+
*/
|
|
1603
|
+
setAccessToken(accessToken) {
|
|
1604
|
+
this.http.setAccessToken(accessToken);
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Get the current configuration
|
|
1608
|
+
*/
|
|
1609
|
+
getConfig() {
|
|
1610
|
+
return { ...this.config };
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Get the API version
|
|
1614
|
+
*/
|
|
1615
|
+
getApiVersion() {
|
|
1616
|
+
return this.config.apiVersion;
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
var InstagramWebhooks = class {
|
|
1620
|
+
/**
|
|
1621
|
+
* Verify that the webhook request came from Meta.
|
|
1622
|
+
* Calculates SHA256 HMAC of the raw body using the App Secret.
|
|
1623
|
+
*
|
|
1624
|
+
* @param body Raw request body as string
|
|
1625
|
+
* @param signature Signature from X-Hub-Signature-256 header (e.g., "sha256=...")
|
|
1626
|
+
* @param appSecret Your Instagram App Secret
|
|
1627
|
+
* @returns true if signature is valid
|
|
1628
|
+
*/
|
|
1629
|
+
static verifySignature(body, signature, appSecret) {
|
|
1630
|
+
if (!signature || !signature.startsWith("sha256=")) {
|
|
1631
|
+
return false;
|
|
1632
|
+
}
|
|
1633
|
+
const expectedSignature = "sha256=" + crypto__default.default.createHmac("sha256", appSecret).update(body).digest("hex");
|
|
1634
|
+
return crypto__default.default.timingSafeEqual(
|
|
1635
|
+
Buffer.from(signature),
|
|
1636
|
+
Buffer.from(expectedSignature)
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Type guard and parser for webhook payloads.
|
|
1641
|
+
*
|
|
1642
|
+
* @param body Parsed JSON body
|
|
1643
|
+
* @returns Typed WebhookPayload or throws error
|
|
1644
|
+
*/
|
|
1645
|
+
static parsePayload(body) {
|
|
1646
|
+
if (body?.object !== "instagram" || !Array.isArray(body?.entry)) {
|
|
1647
|
+
throw new Error("Invalid Instagram webhook payload");
|
|
1648
|
+
}
|
|
1649
|
+
return body;
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
|
|
1653
|
+
// src/types/user.ts
|
|
1654
|
+
var IG_USER_FIELDS = [
|
|
1655
|
+
"id",
|
|
1656
|
+
"account_type",
|
|
1657
|
+
"biography",
|
|
1658
|
+
"followers_count",
|
|
1659
|
+
"follows_count",
|
|
1660
|
+
"media_count",
|
|
1661
|
+
"username",
|
|
1662
|
+
"name",
|
|
1663
|
+
"profile_picture_url",
|
|
1664
|
+
"website",
|
|
1665
|
+
"ig_id"
|
|
1666
|
+
];
|
|
1667
|
+
|
|
1668
|
+
// src/types/media.ts
|
|
1669
|
+
var IG_MEDIA_FIELDS = [
|
|
1670
|
+
"id",
|
|
1671
|
+
"caption",
|
|
1672
|
+
"media_type",
|
|
1673
|
+
"media_product_type",
|
|
1674
|
+
"media_url",
|
|
1675
|
+
"permalink",
|
|
1676
|
+
"thumbnail_url",
|
|
1677
|
+
"timestamp",
|
|
1678
|
+
"username",
|
|
1679
|
+
"like_count",
|
|
1680
|
+
"comments_count",
|
|
1681
|
+
"is_comment_enabled",
|
|
1682
|
+
"is_shared_to_feed",
|
|
1683
|
+
"owner",
|
|
1684
|
+
"shortcode"
|
|
1685
|
+
];
|
|
1686
|
+
|
|
1687
|
+
// src/types/comment.ts
|
|
1688
|
+
var IG_COMMENT_FIELDS = [
|
|
1689
|
+
"id",
|
|
1690
|
+
"text",
|
|
1691
|
+
"username",
|
|
1692
|
+
"timestamp",
|
|
1693
|
+
"like_count",
|
|
1694
|
+
"hidden",
|
|
1695
|
+
"from",
|
|
1696
|
+
"media",
|
|
1697
|
+
"parent_id",
|
|
1698
|
+
"replies"
|
|
1699
|
+
];
|
|
1700
|
+
|
|
1701
|
+
exports.AUTH_ENDPOINTS = AUTH_ENDPOINTS;
|
|
1702
|
+
exports.AuthApi = AuthApi;
|
|
1703
|
+
exports.AuthenticationError = AuthenticationError;
|
|
1704
|
+
exports.COMMENT_ENDPOINTS = COMMENT_ENDPOINTS;
|
|
1705
|
+
exports.CommentsApi = CommentsApi;
|
|
1706
|
+
exports.ConversationsApi = ConversationsApi;
|
|
1707
|
+
exports.HASHTAG_ENDPOINTS = HASHTAG_ENDPOINTS;
|
|
1708
|
+
exports.HashtagsApi = HashtagsApi;
|
|
1709
|
+
exports.HttpClient = HttpClient;
|
|
1710
|
+
exports.IG_COMMENT_FIELDS = IG_COMMENT_FIELDS;
|
|
1711
|
+
exports.IG_MEDIA_FIELDS = IG_MEDIA_FIELDS;
|
|
1712
|
+
exports.IG_USER_FIELDS = IG_USER_FIELDS;
|
|
1713
|
+
exports.INSTAGRAM_BASE_URL = INSTAGRAM_BASE_URL;
|
|
1714
|
+
exports.InsightsApi = InsightsApi;
|
|
1715
|
+
exports.InstagramAPIError = InstagramAPIError;
|
|
1716
|
+
exports.InstagramClient = InstagramClient;
|
|
1717
|
+
exports.InstagramOAuth = InstagramOAuth;
|
|
1718
|
+
exports.InstagramWebhooks = InstagramWebhooks;
|
|
1719
|
+
exports.MEDIA_ENDPOINTS = MEDIA_ENDPOINTS;
|
|
1720
|
+
exports.MESSAGING_ENDPOINTS = MESSAGING_ENDPOINTS;
|
|
1721
|
+
exports.MESSENGER_PROFILE_ENDPOINTS = MESSENGER_PROFILE_ENDPOINTS;
|
|
1722
|
+
exports.MediaApi = MediaApi;
|
|
1723
|
+
exports.MessagingApi = MessagingApi;
|
|
1724
|
+
exports.MessengerProfileApi = MessengerProfileApi;
|
|
1725
|
+
exports.NetworkError = NetworkError;
|
|
1726
|
+
exports.OAUTH_ENDPOINTS = OAUTH_ENDPOINTS;
|
|
1727
|
+
exports.OEMBED_ENDPOINTS = OEMBED_ENDPOINTS;
|
|
1728
|
+
exports.OEmbedApi = OEmbedApi;
|
|
1729
|
+
exports.PUBLISHING_ENDPOINTS = PUBLISHING_ENDPOINTS;
|
|
1730
|
+
exports.PublishingApi = PublishingApi;
|
|
1731
|
+
exports.RateLimitError = RateLimitError;
|
|
1732
|
+
exports.USER_ENDPOINTS = USER_ENDPOINTS;
|
|
1733
|
+
exports.UsersApi = UsersApi;
|
|
1734
|
+
exports.ValidationError = ValidationError;
|
|
1735
|
+
exports.WELCOME_FLOW_ENDPOINTS = WELCOME_FLOW_ENDPOINTS;
|
|
1736
|
+
exports.WebhooksApi = WebhooksApi;
|
|
1737
|
+
exports.WelcomeFlowsApi = WelcomeFlowsApi;
|
|
1738
|
+
exports.buildUrl = buildUrl;
|
|
1739
|
+
exports.formatFields = formatFields;
|
|
1740
|
+
exports.isAuthenticationError = isAuthenticationError;
|
|
1741
|
+
exports.isInstagramAPIError = isInstagramAPIError;
|
|
1742
|
+
exports.isRateLimitError = isRateLimitError;
|
|
1743
|
+
exports.isValidationError = isValidationError;
|
|
1744
|
+
//# sourceMappingURL=index.js.map
|
|
1745
|
+
//# sourceMappingURL=index.js.map
|