@codybrom/denim 1.3.3 → 1.3.4

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 (3) hide show
  1. package/deno.json +1 -1
  2. package/mod.ts +86 -83
  3. package/package.json +1 -1
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codybrom/denim",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "A Deno function for posting to Threads.",
5
5
  "entry": "./mod.ts",
6
6
  "exports": {
package/mod.ts CHANGED
@@ -40,7 +40,7 @@ export interface ThreadsPostRequest {
40
40
  * Creates a Threads media container.
41
41
  *
42
42
  * @param request - The ThreadsPostRequest object containing post details
43
- * @returns A Promise that resolves to an object containing the container ID and media type
43
+ * @returns A Promise that resolves to the container ID
44
44
  * @throws Will throw an error if the API request fails
45
45
  *
46
46
  * @example
@@ -53,12 +53,12 @@ export interface ThreadsPostRequest {
53
53
  * videoUrl: "https://example.com/video.mp4",
54
54
  * altText: "A cool video"
55
55
  * };
56
- * const { containerId, mediaType } = await createThreadsContainer(request);
56
+ * const containerId = await createThreadsContainer(request);
57
57
  * ```
58
58
  */
59
59
  export async function createThreadsContainer(
60
60
  request: ThreadsPostRequest
61
- ): Promise<{ containerId: number; mediaType: string }> {
61
+ ): Promise<string> {
62
62
  // Input validation
63
63
  validateRequest(request);
64
64
 
@@ -80,10 +80,12 @@ export async function createThreadsContainer(
80
80
  }
81
81
 
82
82
  // Handle media type specific parameters
83
- if (request.mediaType === "IMAGE" && request.imageUrl) {
83
+ if (request.mediaType === "VIDEO") {
84
+ const videoItemId = await createVideoItemContainer(request);
85
+ body.set("media_type", "CAROUSEL");
86
+ body.append("children", videoItemId);
87
+ } else if (request.mediaType === "IMAGE" && request.imageUrl) {
84
88
  body.append("image_url", request.imageUrl);
85
- } else if (request.mediaType === "VIDEO" && request.videoUrl) {
86
- body.append("video_url", request.videoUrl);
87
89
  } else if (request.mediaType === "TEXT" && request.linkAttachment) {
88
90
  body.append("link_attachment", request.linkAttachment);
89
91
  } else if (request.mediaType === "CAROUSEL" && request.children) {
@@ -111,7 +113,7 @@ export async function createThreadsContainer(
111
113
 
112
114
  try {
113
115
  const data = JSON.parse(responseText);
114
- return { containerId: parseInt(data.id, 10), mediaType: request.mediaType };
116
+ return data.id;
115
117
  } catch (error) {
116
118
  console.error(`Failed to parse response JSON: ${error}`);
117
119
  throw new Error(`Invalid response from Threads API: ${responseText}`);
@@ -164,6 +166,49 @@ function validateRequest(request: ThreadsPostRequest): void {
164
166
  }
165
167
  }
166
168
 
169
+ /**
170
+ * Creates a video item container for Threads.
171
+ * @param request - The ThreadsPostRequest object containing video post details
172
+ * @returns A Promise that resolves to the video item container ID
173
+ * @throws Will throw an error if the API request fails
174
+ */
175
+ async function createVideoItemContainer(
176
+ request: ThreadsPostRequest
177
+ ): Promise<string> {
178
+ const url = `${THREADS_API_BASE_URL}/${request.userId}/threads`;
179
+ const body = new URLSearchParams({
180
+ access_token: request.accessToken,
181
+ is_carousel_item: "true",
182
+ media_type: "VIDEO",
183
+ video_url: request.videoUrl!,
184
+ ...(request.altText && { alt_text: request.altText }),
185
+ });
186
+
187
+ const response = await fetch(url, {
188
+ method: "POST",
189
+ body: body,
190
+ headers: {
191
+ "Content-Type": "application/x-www-form-urlencoded",
192
+ },
193
+ });
194
+
195
+ const responseText = await response.text();
196
+
197
+ if (!response.ok) {
198
+ throw new Error(
199
+ `Failed to create video item container: ${response.statusText}. Details: ${responseText}`
200
+ );
201
+ }
202
+
203
+ try {
204
+ const data = JSON.parse(responseText);
205
+ return data.id;
206
+ } catch (error) {
207
+ console.error(`Failed to parse response JSON: ${error}`);
208
+ throw new Error(`Invalid response from Threads API: ${responseText}`);
209
+ }
210
+ }
211
+
167
212
  /**
168
213
  * Creates a carousel item for a Threads carousel post.
169
214
  *
@@ -272,52 +317,24 @@ async function checkContainerStatus(
272
317
  *
273
318
  * @param userId - The user ID of the Threads account
274
319
  * @param accessToken - The access token for authentication
275
- * @param containerId - The ID of the container to publish (as a number)
276
- * @param mediaType - The type of media being published
320
+ * @param containerId - The ID of the container to publish
277
321
  * @returns A Promise that resolves to the published container ID
278
- * @throws Will throw an error if the API request fails, if publishing times out, or if the container is not ready (for videos)
322
+ * @throws Will throw an error if the API request fails or if publishing times out
279
323
  *
280
324
  * @example
281
325
  * ```typescript
282
- * const { containerId, mediaType } = await createThreadsContainer(request);
283
- * const publishedId = await publishThreadsContainer("123456", "your_access_token", parseInt(containerId, 10), mediaType);
326
+ * const publishedId = await publishThreadsContainer("123456", "your_access_token", "container_id");
284
327
  * ```
285
328
  */
286
329
  export async function publishThreadsContainer(
287
330
  userId: string,
288
331
  accessToken: string,
289
- containerId: number,
290
- mediaType: string
291
- ): Promise<number> {
292
- if (mediaType === "VIDEO") {
293
- // Check container status before publishing for videos
294
- let status = await checkContainerStatus(
295
- containerId.toString(),
296
- accessToken
297
- );
298
- let attempts = 0;
299
- const maxAttempts = 10; // Increased from 5 to 10 for videos
300
-
301
- while (status !== "FINISHED" && attempts < maxAttempts) {
302
- await new Promise((resolve) => setTimeout(resolve, 30000)); // Wait for 30 seconds
303
- status = await checkContainerStatus(containerId.toString(), accessToken);
304
- attempts++;
305
- console.log(
306
- `Video container status check attempt ${attempts}: ${status}`
307
- );
308
- }
309
-
310
- if (status !== "FINISHED") {
311
- throw new Error(
312
- `Video container not ready after ${maxAttempts} attempts. Current status: ${status}`
313
- );
314
- }
315
- }
316
-
332
+ containerId: string
333
+ ): Promise<string> {
317
334
  const publishUrl = `${THREADS_API_BASE_URL}/${userId}/threads_publish`;
318
335
  const publishBody = new URLSearchParams({
319
336
  access_token: accessToken,
320
- creation_id: containerId.toString(), // Convert to string for URLSearchParams, but it will be sent as a number
337
+ creation_id: containerId,
321
338
  });
322
339
 
323
340
  const publishResponse = await fetch(publishUrl, {
@@ -328,43 +345,35 @@ export async function publishThreadsContainer(
328
345
  },
329
346
  });
330
347
 
331
- const responseText = await publishResponse.text();
332
-
333
348
  if (!publishResponse.ok) {
334
- console.error(`Publish response body: ${responseText}`);
335
349
  throw new Error(
336
- `Failed to publish Threads container: ${publishResponse.statusText}. Details: ${responseText}`
350
+ `Failed to publish Threads container: ${publishResponse.statusText}`
337
351
  );
338
352
  }
339
353
 
340
- // For non-video posts, we still need to check if the post was actually published
341
- if (mediaType !== "VIDEO") {
342
- let status = await checkContainerStatus(
343
- containerId.toString(),
344
- accessToken
345
- );
346
- let attempts = 0;
347
- const maxAttempts = 5;
348
-
349
- while (
350
- status !== "PUBLISHED" &&
351
- status !== "FINISHED" &&
352
- attempts < maxAttempts
353
- ) {
354
- await new Promise((resolve) => setTimeout(resolve, 60000)); // Wait for 1 minute
355
- status = await checkContainerStatus(containerId.toString(), accessToken);
356
- attempts++;
357
- }
354
+ // Check container status
355
+ let status = await checkContainerStatus(containerId, accessToken);
356
+ let attempts = 0;
357
+ const maxAttempts = 5;
358
358
 
359
- if (status === "ERROR") {
360
- throw new Error(`Failed to publish container. Error: ${status}`);
361
- }
359
+ while (
360
+ status !== "PUBLISHED" &&
361
+ status !== "FINISHED" &&
362
+ attempts < maxAttempts
363
+ ) {
364
+ await new Promise((resolve) => setTimeout(resolve, 60000)); // Wait for 1 minute
365
+ status = await checkContainerStatus(containerId, accessToken);
366
+ attempts++;
367
+ }
362
368
 
363
- if (status !== "PUBLISHED" && status !== "FINISHED") {
364
- throw new Error(
365
- `Container not published after ${maxAttempts} attempts. Current status: ${status}`
366
- );
367
- }
369
+ if (status === "ERROR") {
370
+ throw new Error(`Failed to publish container. Error: ${status}`);
371
+ }
372
+
373
+ if (status !== "PUBLISHED" && status !== "FINISHED") {
374
+ throw new Error(
375
+ `Container not published after ${maxAttempts} attempts. Current status: ${status}`
376
+ );
368
377
  }
369
378
 
370
379
  return containerId; // Return the container ID as the published ID
@@ -408,24 +417,18 @@ export function serveRequests() {
408
417
  }
409
418
 
410
419
  // Create the Threads container
411
- const { containerId, mediaType } = await createThreadsContainer(
412
- requestData
413
- );
420
+ const containerId = await createThreadsContainer(requestData);
414
421
 
415
- // Attempt to publish the Threads container
422
+ // Immediately attempt to publish the Threads container
416
423
  const publishedId = await publishThreadsContainer(
417
424
  requestData.userId,
418
425
  requestData.accessToken,
419
- containerId,
420
- mediaType
426
+ containerId
421
427
  );
422
428
 
423
- return new Response(
424
- JSON.stringify({ success: true, publishedId: publishedId.toString() }),
425
- {
426
- headers: { "Content-Type": "application/json" },
427
- }
428
- );
429
+ return new Response(JSON.stringify({ success: true, publishedId }), {
430
+ headers: { "Content-Type": "application/json" },
431
+ });
429
432
  } catch (error) {
430
433
  console.error("Error posting to Threads:", error);
431
434
  return new Response(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codybrom/denim",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Typescript/Deno module to simplify posting to Threads",
5
5
  "main": "mod.ts",
6
6
  "directories": {