operand-meta-sdk 1.2.1

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.
Files changed (113) hide show
  1. package/.eslintrc.js +25 -0
  2. package/.prettierrc +4 -0
  3. package/README.md +435 -0
  4. package/dist/jest.config.d.ts +3 -0
  5. package/dist/jest.config.js +14 -0
  6. package/dist/jest.config.js.map +1 -0
  7. package/dist/src/__test__/mocks/index.d.ts +2 -0
  8. package/dist/src/__test__/mocks/index.js +6 -0
  9. package/dist/src/__test__/mocks/index.js.map +1 -0
  10. package/dist/src/error/operand-error.d.ts +8 -0
  11. package/dist/src/error/operand-error.js +196 -0
  12. package/dist/src/error/operand-error.js.map +1 -0
  13. package/dist/src/index.d.ts +10 -0
  14. package/dist/src/index.js +22 -0
  15. package/dist/src/index.js.map +1 -0
  16. package/dist/src/interfaces/ing-publish.d.ts +53 -0
  17. package/dist/src/interfaces/ing-publish.js +3 -0
  18. package/dist/src/interfaces/ing-publish.js.map +1 -0
  19. package/dist/src/interfaces/meta-auth.d.ts +14 -0
  20. package/dist/src/interfaces/meta-auth.js +3 -0
  21. package/dist/src/interfaces/meta-auth.js.map +1 -0
  22. package/dist/src/interfaces/meta-mkt.d.ts +4 -0
  23. package/dist/src/interfaces/meta-mkt.js +3 -0
  24. package/dist/src/interfaces/meta-mkt.js.map +1 -0
  25. package/dist/src/interfaces/meta-response.d.ts +285 -0
  26. package/dist/src/interfaces/meta-response.js +3 -0
  27. package/dist/src/interfaces/meta-response.js.map +1 -0
  28. package/dist/src/interfaces/meta.d.ts +6 -0
  29. package/dist/src/interfaces/meta.js +3 -0
  30. package/dist/src/interfaces/meta.js.map +1 -0
  31. package/dist/src/interfaces/page-publish.d.ts +66 -0
  32. package/dist/src/interfaces/page-publish.js +3 -0
  33. package/dist/src/interfaces/page-publish.js.map +1 -0
  34. package/dist/src/modules/auth/meta-auth.d.ts +35 -0
  35. package/dist/src/modules/auth/meta-auth.js +131 -0
  36. package/dist/src/modules/auth/meta-auth.js.map +1 -0
  37. package/dist/src/modules/auth/meta-auth.spec.d.ts +1 -0
  38. package/dist/src/modules/auth/meta-auth.spec.js +76 -0
  39. package/dist/src/modules/auth/meta-auth.spec.js.map +1 -0
  40. package/dist/src/modules/comments/ing-comments.d.ts +7 -0
  41. package/dist/src/modules/comments/ing-comments.js +27 -0
  42. package/dist/src/modules/comments/ing-comments.js.map +1 -0
  43. package/dist/src/modules/comments/page-comments.d.ts +6 -0
  44. package/dist/src/modules/comments/page-comments.js +18 -0
  45. package/dist/src/modules/comments/page-comments.js.map +1 -0
  46. package/dist/src/modules/insights/ing-insights.d.ts +42 -0
  47. package/dist/src/modules/insights/ing-insights.js +150 -0
  48. package/dist/src/modules/insights/ing-insights.js.map +1 -0
  49. package/dist/src/modules/insights/mkt-insights.d.ts +10 -0
  50. package/dist/src/modules/insights/mkt-insights.js +46 -0
  51. package/dist/src/modules/insights/mkt-insights.js.map +1 -0
  52. package/dist/src/modules/insights/page-insights.d.ts +34 -0
  53. package/dist/src/modules/insights/page-insights.js +146 -0
  54. package/dist/src/modules/insights/page-insights.js.map +1 -0
  55. package/dist/src/modules/meta-ing.d.ts +5 -0
  56. package/dist/src/modules/meta-ing.js +11 -0
  57. package/dist/src/modules/meta-ing.js.map +1 -0
  58. package/dist/src/modules/meta-mkt.d.ts +5 -0
  59. package/dist/src/modules/meta-mkt.js +11 -0
  60. package/dist/src/modules/meta-mkt.js.map +1 -0
  61. package/dist/src/modules/meta-page.d.ts +5 -0
  62. package/dist/src/modules/meta-page.js +11 -0
  63. package/dist/src/modules/meta-page.js.map +1 -0
  64. package/dist/src/modules/meta.d.ts +10 -0
  65. package/dist/src/modules/meta.js +23 -0
  66. package/dist/src/modules/meta.js.map +1 -0
  67. package/dist/src/modules/publish/ing-publish.d.ts +39 -0
  68. package/dist/src/modules/publish/ing-publish.js +464 -0
  69. package/dist/src/modules/publish/ing-publish.js.map +1 -0
  70. package/dist/src/modules/publish/page-publish.d.ts +51 -0
  71. package/dist/src/modules/publish/page-publish.js +560 -0
  72. package/dist/src/modules/publish/page-publish.js.map +1 -0
  73. package/dist/src/modules/publish/page-publish.spec.d.ts +1 -0
  74. package/dist/src/modules/publish/page-publish.spec.js +280 -0
  75. package/dist/src/modules/publish/page-publish.spec.js.map +1 -0
  76. package/dist/src/modules/utils/meta-utils.d.ts +8 -0
  77. package/dist/src/modules/utils/meta-utils.js +28 -0
  78. package/dist/src/modules/utils/meta-utils.js.map +1 -0
  79. package/dist/src/utils/api.d.ts +8 -0
  80. package/dist/src/utils/api.js +36 -0
  81. package/dist/src/utils/api.js.map +1 -0
  82. package/dist/tsconfig.tsbuildinfo +1 -0
  83. package/jest.config.ts +198 -0
  84. package/package.json +39 -0
  85. package/src/__test__/mocks/image.jpeg +0 -0
  86. package/src/__test__/mocks/index.ts +5 -0
  87. package/src/__test__/mocks/video-to-post.mp4 +0 -0
  88. package/src/__test__/mocks/video-to-stories.mp4 +0 -0
  89. package/src/error/operand-error.ts +217 -0
  90. package/src/index.ts +21 -0
  91. package/src/interfaces/ing-publish.ts +58 -0
  92. package/src/interfaces/meta-auth.ts +52 -0
  93. package/src/interfaces/meta-mkt.ts +5 -0
  94. package/src/interfaces/meta-response.ts +319 -0
  95. package/src/interfaces/meta.ts +7 -0
  96. package/src/interfaces/page-publish.ts +72 -0
  97. package/src/modules/auth/meta-auth.spec.ts +93 -0
  98. package/src/modules/auth/meta-auth.ts +227 -0
  99. package/src/modules/comments/ing-comments.ts +38 -0
  100. package/src/modules/comments/page-comments.ts +20 -0
  101. package/src/modules/insights/ing-insights.ts +275 -0
  102. package/src/modules/insights/mkt-insights.ts +68 -0
  103. package/src/modules/insights/page-insights.ts +267 -0
  104. package/src/modules/meta-ing.ts +8 -0
  105. package/src/modules/meta-mkt.ts +8 -0
  106. package/src/modules/meta-page.ts +8 -0
  107. package/src/modules/meta.ts +31 -0
  108. package/src/modules/publish/ing-publish.ts +754 -0
  109. package/src/modules/publish/page-publish.spec.ts +386 -0
  110. package/src/modules/publish/page-publish.ts +881 -0
  111. package/src/modules/utils/meta-utils.ts +37 -0
  112. package/src/utils/api.ts +45 -0
  113. package/tsconfig.json +22 -0
@@ -0,0 +1,754 @@
1
+ import {
2
+ ConstructorIng,
3
+ CreatePhotoStory,
4
+ CreatePost,
5
+ CreateStories,
6
+ CreateVideoStory,
7
+ IIngPublish,
8
+ PhotoMediaItem,
9
+ saveMediaInMetaIngContainer,
10
+ VideoMediaItem,
11
+ } from "../../interfaces/ing-publish";
12
+ import { OperandError } from "../../error/operand-error";
13
+ import {
14
+ GetStatusMediaContainerDownloadResponse,
15
+ PostMediaContainerReelsResponse,
16
+ SaveMediaStorageResponse,
17
+ } from "../../interfaces/meta-response";
18
+ import * as FileType from "file-type";
19
+ import * as fs from "node:fs";
20
+ import { MetaUtils } from "../utils/meta-utils";
21
+ import * as path from "node:path";
22
+ import * as ffmpeg from "fluent-ffmpeg";
23
+
24
+ export class IngPublish extends MetaUtils implements IIngPublish {
25
+ protected readonly ingId: string;
26
+
27
+ constructor({
28
+ apiVersion,
29
+ ingId,
30
+ pageAccessToken,
31
+ typeToken,
32
+ }: ConstructorIng) {
33
+ super({
34
+ apiVersion,
35
+ pageAccessToken,
36
+ isInstagramApi: typeToken === "ig",
37
+ });
38
+ this.ingId = ingId;
39
+ }
40
+
41
+ private fileTypesPermitted(file: "video" | "photo", type: string): boolean {
42
+ return file === "photo"
43
+ ? ["jpeg", "jpg", "png", "gif", "bmp", "tiff", "webp"].includes(type)
44
+ : ["mp4", "avi", "flv", "mkv", "mov", "mpeg", "wmv"].includes(type);
45
+ }
46
+
47
+ public static async verifyVideoSpec(
48
+ videoSource: Buffer | string,
49
+ ext: string,
50
+ to: "reels" | "post" | "stories",
51
+ ): Promise<{
52
+ success: boolean;
53
+ warn?: {
54
+ videoChecks: {
55
+ chromaSubsampling: boolean;
56
+ fixedFrameRate: boolean;
57
+ progressive: boolean;
58
+ ratio: boolean;
59
+ };
60
+ audioChecks: {
61
+ bitrate: boolean;
62
+ channels: boolean;
63
+ sampleRate: boolean;
64
+ };
65
+ };
66
+ error?: string;
67
+ }> {
68
+ let tempFilePath = "";
69
+
70
+ const isBuffer = videoSource instanceof Buffer;
71
+
72
+ if (isBuffer) {
73
+ tempFilePath = path.resolve(
74
+ __dirname,
75
+ "..",
76
+ "..",
77
+ "temp",
78
+ `${Date.now()}.${ext}`,
79
+ );
80
+ await fs.promises.writeFile(tempFilePath, videoSource);
81
+ } else {
82
+ tempFilePath = videoSource as string;
83
+ }
84
+
85
+ const videoSpecResponse = await new Promise<{
86
+ success: boolean;
87
+ warn?: {
88
+ videoChecks: {
89
+ chromaSubsampling: boolean;
90
+ fixedFrameRate: boolean;
91
+ progressive: boolean;
92
+ ratio: boolean;
93
+ };
94
+ audioChecks: {
95
+ bitrate: boolean;
96
+ channels: boolean;
97
+ sampleRate: boolean;
98
+ };
99
+ };
100
+ error?: string;
101
+ }>((resolve, reject) => {
102
+ ffmpeg.ffprobe(tempFilePath, (err, metadata) => {
103
+ if (err) {
104
+ reject(err);
105
+ }
106
+
107
+ const stream = metadata.streams.find((s) => s.width && s.height);
108
+
109
+ const width = stream?.width;
110
+ const height = stream?.height;
111
+ const fpsString = stream?.avg_frame_rate;
112
+ const fps = fpsString ? eval(fpsString) : 0;
113
+ const ratio = width / height;
114
+ const size = metadata.format.size;
115
+ const duration = metadata.format.duration;
116
+
117
+ const isStories = to === "stories";
118
+
119
+ const validFps = fps && fps >= 23 && fps <= 60;
120
+
121
+ if (!validFps) {
122
+ resolve({
123
+ success: false,
124
+ error: `Invalid fps. The video must have between 23 and 60 fps. The current fps is ${fps}.`,
125
+ });
126
+ }
127
+
128
+ const validSize = size < 1024 * 1024 * 1024;
129
+
130
+ if (!validSize) {
131
+ resolve({
132
+ success: false,
133
+ error: `Invalid size. The video must be a maximum of 10 gigabytes.`,
134
+ });
135
+ }
136
+
137
+ const validDuration = isStories ? duration <= 60 : duration <= 900;
138
+
139
+ if (!validDuration) {
140
+ resolve({
141
+ success: false,
142
+ error: `Invalid duration. The video must be a maximum of ${isStories ? "60 seconds" : "900 minutes"}.`,
143
+ });
144
+ }
145
+
146
+ const videoStream = metadata.streams.find(
147
+ (s) => s.codec_type === "video",
148
+ );
149
+ const audioStream = metadata.streams.find(
150
+ (s) => s.codec_type === "audio",
151
+ );
152
+
153
+ const validVideoCodec = ["h264", "hevc"].includes(
154
+ videoStream?.codec_name ?? "",
155
+ );
156
+
157
+ const validAudioCodec = ["aac"].includes(audioStream?.codec_name ?? "");
158
+
159
+ if (!validVideoCodec) {
160
+ resolve({
161
+ success: false,
162
+ error: `Invalid codecs. The video must have the following video codecs: h264, hevc, vp9, av1`,
163
+ });
164
+ }
165
+
166
+ if (!validAudioCodec) {
167
+ resolve({
168
+ success: false,
169
+ error: `Invalid codecs. The video must be have aac audio codec`,
170
+ });
171
+ }
172
+
173
+ const videoChecks = {
174
+ chromaSubsampling: videoStream?.pix_fmt === "yuv420p",
175
+ fixedFrameRate:
176
+ videoStream?.avg_frame_rate === videoStream?.r_frame_rate,
177
+ progressive:
178
+ videoStream?.field_order === "progressive" ||
179
+ videoStream.progressive === "1" ||
180
+ videoStream.progressive === true ||
181
+ (!videoStream.interlaced &&
182
+ !videoStream.top_field_first &&
183
+ !videoStream.bottom_field_first) ||
184
+ videoStream.interlaced === "0" ||
185
+ videoStream.interlaced === false,
186
+ ratio: ratio <= 0.5625,
187
+ };
188
+
189
+ const audioChecks = {
190
+ bitrate: parseInt(audioStream?.bit_rate ?? "0") >= 128000,
191
+ channels: audioStream?.channels === 2,
192
+ sampleRate: audioStream?.sample_rate === 48000,
193
+ };
194
+
195
+ resolve({
196
+ success: true,
197
+ warn: { videoChecks, audioChecks },
198
+ });
199
+
200
+ resolve({
201
+ success: true,
202
+ });
203
+ });
204
+ });
205
+
206
+ if (isBuffer) await fs.promises.unlink(tempFilePath);
207
+
208
+ return videoSpecResponse;
209
+ }
210
+
211
+ private async verifyPhotoSize(
212
+ value: string | Buffer,
213
+ isBuffer: boolean,
214
+ ): Promise<boolean> {
215
+ if (isBuffer) {
216
+ return (value as Buffer).length / 1024 / 1024 <= 8;
217
+ }
218
+
219
+ const status = await fs.promises.stat(value as string);
220
+ return status.size / 1024 / 1024 <= 8;
221
+ }
222
+
223
+ private verifyStatusCodeContainerVideoDownload = async (id: string) => {
224
+ let statusCodeContainer = 1;
225
+
226
+ while (statusCodeContainer === 1) {
227
+ const response =
228
+ await this.api.get<GetStatusMediaContainerDownloadResponse>(`/${id}`, {
229
+ params: {
230
+ access_token: this.pageAccessToken,
231
+ fields: "status_code",
232
+ },
233
+ });
234
+
235
+ const status = response?.data?.status_code;
236
+
237
+ if (status === "IN_PROGRESS") {
238
+ await new Promise((resolve) => setTimeout(resolve, 10000));
239
+ continue;
240
+ }
241
+
242
+ if (status === "ERROR") {
243
+ throw new OperandError({
244
+ message: "Media with problems",
245
+ });
246
+ }
247
+
248
+ if (status === "FINISHED") {
249
+ statusCodeContainer = 0;
250
+ await new Promise((resolve) => setTimeout(resolve, 5000));
251
+ break;
252
+ }
253
+ }
254
+ };
255
+
256
+ private verifyVideoSize = async (
257
+ value: string | Buffer,
258
+ isBuffer: boolean,
259
+ ): Promise<boolean> => {
260
+ if (isBuffer) {
261
+ return (value as Buffer).length / 1024 / 1024 / 1024 <= 1;
262
+ }
263
+
264
+ const status = await fs.promises.stat(value as string);
265
+ return status.size / 1024 / 1024 / 1024 <= 1;
266
+ };
267
+
268
+ private savePhotoInMetaContainerByUrl = async ({
269
+ to,
270
+ value: url,
271
+ isCarouselItem,
272
+ caption,
273
+ coverUrl,
274
+ thumbOffset,
275
+ collaborators,
276
+ userTags,
277
+ }: saveMediaInMetaIngContainer): Promise<string> => {
278
+ const response = await fetch(url);
279
+ const arrayBuffer = await response.arrayBuffer();
280
+
281
+ const fileType = await FileType.fromBuffer(arrayBuffer);
282
+
283
+ if (!fileType) {
284
+ throw new OperandError({
285
+ message: "Impossible to get the file type of file.",
286
+ });
287
+ }
288
+
289
+ if (!this.fileTypesPermitted("photo", fileType.ext)) {
290
+ throw new OperandError({
291
+ message:
292
+ "This file type is not permitted. File types permitted: jpeg, jpg, png, gif, bmp, tiff, webp",
293
+ });
294
+ }
295
+
296
+ if (!(await this.verifyPhotoSize(Buffer.from(arrayBuffer), true))) {
297
+ throw new OperandError({
298
+ message: "The photo must be less or equal to 8MB.",
299
+ });
300
+ }
301
+
302
+ const containerId = (
303
+ await this.api.post<SaveMediaStorageResponse>(
304
+ `${this.ingId}/media`,
305
+ undefined,
306
+ {
307
+ params: {
308
+ image_url: url,
309
+ ...(isCarouselItem ? { is_carousel_item: true } : {}),
310
+ ...(to === "REELS" ? { media_type: "REELS" } : {}),
311
+ ...(to === "STORIES" ? { media_type: "STORIES" } : {}),
312
+ access_token: this.pageAccessToken,
313
+ ...(caption ? { caption } : {}),
314
+ ...(coverUrl ? { cover_url: coverUrl } : {}),
315
+ ...(thumbOffset ? { thumb_offset: thumbOffset } : {}),
316
+ ...(userTags ? { user_tags: JSON.stringify(userTags) } : {}),
317
+ ...(collaborators
318
+ ? { collaborators: JSON.stringify(collaborators) }
319
+ : {}),
320
+ },
321
+ },
322
+ )
323
+ ).data.id;
324
+
325
+ await this.verifyStatusCodeContainerVideoDownload(containerId);
326
+
327
+ return containerId;
328
+ };
329
+
330
+ private saveVideoInMetaContainerByUrl = async ({
331
+ to,
332
+ value: url,
333
+ caption,
334
+ isCarouselItem,
335
+ coverUrl,
336
+ thumbOffset,
337
+ collaborators,
338
+ userTags,
339
+ }: saveMediaInMetaIngContainer): Promise<string> => {
340
+ const response = await fetch(url);
341
+ const arrayBuffer = await response.arrayBuffer();
342
+
343
+ const fileType = await FileType.fromBuffer(arrayBuffer);
344
+
345
+ if (!fileType) {
346
+ throw new OperandError({
347
+ message: "Impossible to get the file type of file.",
348
+ });
349
+ }
350
+
351
+ if (!this.fileTypesPermitted("video", fileType.ext)) {
352
+ throw new OperandError({
353
+ message:
354
+ "Invalid file type. File types permitted: mp4, avi, flv, mkv, mov, mpeg, wmv",
355
+ });
356
+ }
357
+
358
+ if (!(await this.verifyVideoSize(Buffer.from(arrayBuffer), true))) {
359
+ throw new OperandError({
360
+ message: "The video must be less or equal to 1GB.",
361
+ });
362
+ }
363
+
364
+ const containerId = (
365
+ await this.api.post<SaveMediaStorageResponse>(
366
+ `${this.ingId}/media`,
367
+ undefined,
368
+ {
369
+ params: {
370
+ video_url: url,
371
+ access_token: this.pageAccessToken,
372
+ ...(isCarouselItem ? { is_carousel_item: true } : {}),
373
+ ...(to === "STORIES" ? { media_type: "STORIES" } : { media_type: "REELS" }),
374
+ ...(caption ? { caption } : {}),
375
+ ...(coverUrl ? { cover_url: coverUrl } : {}),
376
+ ...(thumbOffset ? { thumb_offset: thumbOffset } : {}),
377
+ ...(userTags ? { user_tags: JSON.stringify(userTags) } : {}),
378
+ ...(collaborators
379
+ ? { collaborators: JSON.stringify(collaborators) }
380
+ : {}),
381
+ },
382
+ },
383
+ )
384
+ ).data.id;
385
+
386
+ await this.verifyStatusCodeContainerVideoDownload(containerId);
387
+
388
+ return containerId;
389
+ };
390
+
391
+ private saveVideoInMetaContainerByPath = async ({
392
+ to,
393
+ value: path,
394
+ caption,
395
+ isCarouselItem,
396
+ coverUrl,
397
+ thumbOffset,
398
+ collaborators,
399
+ userTags,
400
+ }: saveMediaInMetaIngContainer): Promise<string> => {
401
+ const arrayBuffer = await fs.promises.readFile(path);
402
+
403
+ const fileType = await FileType.fromBuffer(arrayBuffer);
404
+
405
+ if (!fileType) {
406
+ throw new OperandError({
407
+ message: "Impossible to get the file type of file.",
408
+ });
409
+ }
410
+
411
+ if (!this.fileTypesPermitted("video", fileType.ext)) {
412
+ throw new OperandError({
413
+ message:
414
+ "Invalid file type. File types permitted: mp4, avi, flv, mkv, mov, mpeg, wmv",
415
+ });
416
+ }
417
+
418
+ if (!(await this.verifyVideoSize(path, false))) {
419
+ throw new OperandError({
420
+ message: "The video must be less or equal to 1GB.",
421
+ });
422
+ }
423
+
424
+ const {
425
+ data: { id, uri },
426
+ } = await this.api.post<PostMediaContainerReelsResponse>(
427
+ `${this.ingId}/media`,
428
+ undefined,
429
+ {
430
+ params: {
431
+ ...(isCarouselItem ? { is_carousel_item: true } : {}),
432
+ ...(to === "STORIES" ? { media_type: "STORIES" } : { media_type: "REELS" }),
433
+ upload_type: "resumable",
434
+ access_token: this.pageAccessToken,
435
+ ...(caption ? { caption } : {}),
436
+ ...(coverUrl ? { cover_url: coverUrl } : {}),
437
+ ...(thumbOffset ? { thumb_offset: thumbOffset } : {}),
438
+ ...(userTags ? { user_tags: JSON.stringify(userTags) } : {}),
439
+ ...(collaborators
440
+ ? { collaborators: JSON.stringify(collaborators) }
441
+ : {}),
442
+ },
443
+ },
444
+ );
445
+
446
+ const sessionStart = await fetch(uri, {
447
+ method: "POST",
448
+ headers: {
449
+ Authorization: `OAuth ${this.pageAccessToken}`,
450
+ offset: "0",
451
+ file_size: String(arrayBuffer.byteLength),
452
+ },
453
+ body: arrayBuffer,
454
+ });
455
+
456
+ if (sessionStart.status !== 200) {
457
+ throw new OperandError({
458
+ message: "Error on upload video",
459
+ });
460
+ }
461
+
462
+ await this.verifyStatusCodeContainerVideoDownload(id);
463
+
464
+ return id;
465
+ };
466
+
467
+ private uploadPhotos = async (
468
+ photos: PhotoMediaItem[],
469
+ ): Promise<string[]> => {
470
+ try {
471
+ const medias = [];
472
+
473
+ for (const photo of photos) {
474
+ medias.push(
475
+ await this.savePhotoInMetaContainerByUrl({
476
+ value: photo.value,
477
+ to: "FEED",
478
+ isCarouselItem: true,
479
+ }),
480
+ );
481
+ }
482
+
483
+ return medias;
484
+ } catch (error) {
485
+ throw new OperandError({
486
+ message: "Error when upload photos",
487
+ error,
488
+ });
489
+ }
490
+ };
491
+
492
+ private uploadVideos = async (
493
+ videos: VideoMediaItem[],
494
+ ): Promise<string[]> => {
495
+ try {
496
+ const medias = [];
497
+
498
+ for (const video of videos) {
499
+ const data = {
500
+ to: "FEED" as const,
501
+ value: video.value,
502
+ isCarouselItem: true,
503
+ };
504
+
505
+ medias.push(
506
+ video.source === "url"
507
+ ? await this.saveVideoInMetaContainerByUrl(data)
508
+ : await this.saveVideoInMetaContainerByPath(data),
509
+ );
510
+ }
511
+
512
+ return medias;
513
+ } catch (error) {
514
+ throw new OperandError({
515
+ message: "Error when upload videos",
516
+ error,
517
+ });
518
+ }
519
+ };
520
+
521
+ private createUniquePost = async (post: CreatePost): Promise<string> => {
522
+ try {
523
+ const {
524
+ medias,
525
+ caption,
526
+ coverUrl,
527
+ thumbOffset,
528
+ collaborators,
529
+ userTags,
530
+ } = post;
531
+
532
+ const mediaType = coverUrl ? "REELS" : "FEED";
533
+
534
+ const containerId =
535
+ medias[0].mediaType === "photo"
536
+ ? await this.savePhotoInMetaContainerByUrl({
537
+ value: medias[0].value,
538
+ to: mediaType,
539
+ caption,
540
+ coverUrl,
541
+ thumbOffset,
542
+ collaborators,
543
+ userTags,
544
+ })
545
+ : medias[0].source === "url"
546
+ ? await this.saveVideoInMetaContainerByUrl({
547
+ to: mediaType,
548
+ value: medias[0].value,
549
+ caption,
550
+ coverUrl,
551
+ thumbOffset,
552
+ collaborators,
553
+ userTags,
554
+ })
555
+ : await this.saveVideoInMetaContainerByPath({
556
+ to: mediaType,
557
+ value: medias[0].value,
558
+ caption,
559
+ coverUrl,
560
+ thumbOffset,
561
+ collaborators,
562
+ userTags,
563
+ });
564
+
565
+ await this.verifyStatusCodeContainerVideoDownload(containerId);
566
+
567
+ return (
568
+ await this.api.post<SaveMediaStorageResponse>(
569
+ `/${this.ingId}/media_publish`,
570
+ undefined,
571
+ {
572
+ params: {
573
+ creation_id: containerId,
574
+ access_token: this.pageAccessToken,
575
+ },
576
+ },
577
+ )
578
+ ).data.id;
579
+ } catch (error) {
580
+ throw new OperandError({
581
+ message: "Error when upload create unique post",
582
+ error,
583
+ });
584
+ }
585
+ };
586
+
587
+ private createCarrouselPost = async (post: CreatePost): Promise<string> => {
588
+ try {
589
+ const { caption, medias } = post;
590
+
591
+ const photoIds = await this.uploadPhotos(
592
+ medias.filter((m) => m.mediaType === "photo"),
593
+ );
594
+
595
+ const videoIds = await this.uploadVideos(
596
+ medias.filter((m) => m.mediaType === "video"),
597
+ );
598
+
599
+ const media = [...photoIds, ...videoIds];
600
+
601
+ const creationId = (
602
+ await this.api.post<SaveMediaStorageResponse>(
603
+ `${this.ingId}/media`,
604
+ undefined,
605
+ {
606
+ params: {
607
+ access_token: this.pageAccessToken,
608
+ caption,
609
+ children: media.join(","),
610
+ media_type: "CAROUSEL",
611
+ },
612
+ },
613
+ )
614
+ ).data.id;
615
+
616
+ await this.verifyStatusCodeContainerVideoDownload(creationId);
617
+
618
+ return (
619
+ await this.api.post(`${this.ingId}/media_publish`, undefined, {
620
+ params: {
621
+ creation_id: creationId,
622
+ access_token: this.pageAccessToken,
623
+ },
624
+ })
625
+ ).data.id;
626
+ } catch (error) {
627
+ throw new OperandError({
628
+ message: "Error when upload create carrousel post",
629
+ error,
630
+ });
631
+ }
632
+ };
633
+
634
+ public async createPost(post: CreatePost): Promise<string> {
635
+ await this.createTempFolder();
636
+
637
+ const { medias } = post;
638
+
639
+ if (medias.length === 0) {
640
+ throw new OperandError({
641
+ message: "Medias is required",
642
+ });
643
+ }
644
+
645
+ if (medias.length > 10) {
646
+ throw new OperandError({
647
+ message: "Medias must be less than or equal to 10",
648
+ });
649
+ }
650
+
651
+ if (medias.length === 1) {
652
+ return this.createUniquePost(post);
653
+ }
654
+
655
+ return this.createCarrouselPost(post);
656
+ }
657
+
658
+ private async createPhotoStory(photo: CreatePhotoStory): Promise<string> {
659
+ try {
660
+ const photoId = await this.savePhotoInMetaContainerByUrl({
661
+ value: photo.value,
662
+ to: "STORIES",
663
+ userTags: photo.userTags,
664
+ });
665
+
666
+ await this.verifyStatusCodeContainerVideoDownload(photoId);
667
+
668
+ return (
669
+ await this.api.post<SaveMediaStorageResponse>(
670
+ `${this.ingId}/media_publish`,
671
+ undefined,
672
+ {
673
+ params: {
674
+ creation_id: photoId,
675
+ access_token: this.pageAccessToken,
676
+ },
677
+ },
678
+ )
679
+ ).data.id;
680
+ } catch (error) {
681
+ throw new OperandError({
682
+ message: "Error in create photo stories",
683
+ error,
684
+ });
685
+ }
686
+ }
687
+
688
+ private async createVideoStory(video: CreateVideoStory): Promise<string> {
689
+ try {
690
+ const data = {
691
+ to: "STORIES" as const,
692
+ value: video.value,
693
+ userTags: video.userTags,
694
+ };
695
+
696
+ const videoId =
697
+ video.source === "url"
698
+ ? await this.saveVideoInMetaContainerByUrl(data)
699
+ : await this.saveVideoInMetaContainerByPath(data);
700
+
701
+ await this.verifyStatusCodeContainerVideoDownload(videoId);
702
+
703
+ return (
704
+ await this.api.post<SaveMediaStorageResponse>(
705
+ `${this.ingId}/media_publish`,
706
+ undefined,
707
+ {
708
+ params: {
709
+ creation_id: videoId,
710
+ access_token: this.pageAccessToken,
711
+ },
712
+ },
713
+ )
714
+ ).data.id;
715
+ } catch (error) {
716
+ throw new OperandError({
717
+ message: "Error in create video stories",
718
+ error,
719
+ });
720
+ }
721
+ }
722
+
723
+ public async createStories(media: CreateStories): Promise<string> {
724
+ await this.createTempFolder();
725
+
726
+ if (!media) {
727
+ throw new OperandError({
728
+ message: "Media is required",
729
+ });
730
+ }
731
+
732
+ if (media.mediaType === "photo") return this.createPhotoStory(media);
733
+
734
+ return this.createVideoStory(media);
735
+ }
736
+
737
+ public async getLinkPost(id: string): Promise<string> {
738
+ try {
739
+ return (
740
+ await this.api.get<{ permalink: string; id: string }>(`${id}`, {
741
+ params: {
742
+ fields: "permalink",
743
+ access_token: this.pageAccessToken,
744
+ },
745
+ })
746
+ ).data.permalink;
747
+ } catch (error) {
748
+ throw new OperandError({
749
+ message: "Error in get link post",
750
+ error,
751
+ });
752
+ }
753
+ }
754
+ }