@codybrom/denim 1.3.2 → 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 +80 -68
  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.2",
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: string; 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: data.id, 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
  *
@@ -273,44 +318,19 @@ async function checkContainerStatus(
273
318
  * @param userId - The user ID of the Threads account
274
319
  * @param accessToken - The access token for authentication
275
320
  * @param containerId - The ID of the container to publish
276
- * @param mediaType - The type of media being published
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", containerId, 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: string,
290
- mediaType: string
332
+ containerId: string
291
333
  ): Promise<string> {
292
- if (mediaType === "VIDEO") {
293
- // Check container status before publishing for videos
294
- let status = await checkContainerStatus(containerId, accessToken);
295
- let attempts = 0;
296
- const maxAttempts = 10; // Increased from 5 to 10 for videos
297
-
298
- while (status !== "FINISHED" && attempts < maxAttempts) {
299
- await new Promise((resolve) => setTimeout(resolve, 30000)); // Wait for 30 seconds
300
- status = await checkContainerStatus(containerId, accessToken);
301
- attempts++;
302
- console.log(
303
- `Video container status check attempt ${attempts}: ${status}`
304
- );
305
- }
306
-
307
- if (status !== "FINISHED") {
308
- throw new Error(
309
- `Video container not ready after ${maxAttempts} attempts. Current status: ${status}`
310
- );
311
- }
312
- }
313
-
314
334
  const publishUrl = `${THREADS_API_BASE_URL}/${userId}/threads_publish`;
315
335
  const publishBody = new URLSearchParams({
316
336
  access_token: accessToken,
@@ -325,40 +345,35 @@ export async function publishThreadsContainer(
325
345
  },
326
346
  });
327
347
 
328
- const responseText = await publishResponse.text();
329
-
330
348
  if (!publishResponse.ok) {
331
- console.error(`Publish response body: ${responseText}`);
332
349
  throw new Error(
333
- `Failed to publish Threads container: ${publishResponse.statusText}. Details: ${responseText}`
350
+ `Failed to publish Threads container: ${publishResponse.statusText}`
334
351
  );
335
352
  }
336
353
 
337
- // For non-video posts, we still need to check if the post was actually published
338
- if (mediaType !== "VIDEO") {
339
- let status = await checkContainerStatus(containerId, accessToken);
340
- let attempts = 0;
341
- const maxAttempts = 5;
342
-
343
- while (
344
- status !== "PUBLISHED" &&
345
- status !== "FINISHED" &&
346
- attempts < maxAttempts
347
- ) {
348
- await new Promise((resolve) => setTimeout(resolve, 60000)); // Wait for 1 minute
349
- status = await checkContainerStatus(containerId, accessToken);
350
- attempts++;
351
- }
354
+ // Check container status
355
+ let status = await checkContainerStatus(containerId, accessToken);
356
+ let attempts = 0;
357
+ const maxAttempts = 5;
352
358
 
353
- if (status === "ERROR") {
354
- throw new Error(`Failed to publish container. Error: ${status}`);
355
- }
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
+ }
356
368
 
357
- if (status !== "PUBLISHED" && status !== "FINISHED") {
358
- throw new Error(
359
- `Container not published after ${maxAttempts} attempts. Current status: ${status}`
360
- );
361
- }
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
+ );
362
377
  }
363
378
 
364
379
  return containerId; // Return the container ID as the published ID
@@ -402,16 +417,13 @@ export function serveRequests() {
402
417
  }
403
418
 
404
419
  // Create the Threads container
405
- const { containerId, mediaType } = await createThreadsContainer(
406
- requestData
407
- );
420
+ const containerId = await createThreadsContainer(requestData);
408
421
 
409
- // Attempt to publish the Threads container
422
+ // Immediately attempt to publish the Threads container
410
423
  const publishedId = await publishThreadsContainer(
411
424
  requestData.userId,
412
425
  requestData.accessToken,
413
- containerId,
414
- mediaType
426
+ containerId
415
427
  );
416
428
 
417
429
  return new Response(JSON.stringify({ success: true, publishedId }), {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codybrom/denim",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Typescript/Deno module to simplify posting to Threads",
5
5
  "main": "mod.ts",
6
6
  "directories": {