Skip to content

Commit

Permalink
revert to 1.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
codybrom committed Sep 13, 2024
1 parent 46a21df commit 4d0561f
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 85 deletions.
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codybrom/denim",
"version": "1.3.3",
"version": "1.3.4",
"description": "A Deno function for posting to Threads.",
"entry": "./mod.ts",
"exports": {
Expand Down
169 changes: 86 additions & 83 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface ThreadsPostRequest {
* Creates a Threads media container.
*
* @param request - The ThreadsPostRequest object containing post details
* @returns A Promise that resolves to an object containing the container ID and media type
* @returns A Promise that resolves to the container ID
* @throws Will throw an error if the API request fails
*
* @example
Expand All @@ -53,12 +53,12 @@ export interface ThreadsPostRequest {
* videoUrl: "https://example.com/video.mp4",
* altText: "A cool video"
* };
* const { containerId, mediaType } = await createThreadsContainer(request);
* const containerId = await createThreadsContainer(request);
* ```
*/
export async function createThreadsContainer(
request: ThreadsPostRequest
): Promise<{ containerId: number; mediaType: string }> {
): Promise<string> {
// Input validation
validateRequest(request);

Expand All @@ -80,10 +80,12 @@ export async function createThreadsContainer(
}

// Handle media type specific parameters
if (request.mediaType === "IMAGE" && request.imageUrl) {
if (request.mediaType === "VIDEO") {
const videoItemId = await createVideoItemContainer(request);
body.set("media_type", "CAROUSEL");
body.append("children", videoItemId);
} else if (request.mediaType === "IMAGE" && request.imageUrl) {
body.append("image_url", request.imageUrl);
} else if (request.mediaType === "VIDEO" && request.videoUrl) {
body.append("video_url", request.videoUrl);
} else if (request.mediaType === "TEXT" && request.linkAttachment) {
body.append("link_attachment", request.linkAttachment);
} else if (request.mediaType === "CAROUSEL" && request.children) {
Expand Down Expand Up @@ -111,7 +113,7 @@ export async function createThreadsContainer(

try {
const data = JSON.parse(responseText);
return { containerId: parseInt(data.id, 10), mediaType: request.mediaType };
return data.id;
} catch (error) {
console.error(`Failed to parse response JSON: ${error}`);
throw new Error(`Invalid response from Threads API: ${responseText}`);
Expand Down Expand Up @@ -164,6 +166,49 @@ function validateRequest(request: ThreadsPostRequest): void {
}
}

/**
* Creates a video item container for Threads.
* @param request - The ThreadsPostRequest object containing video post details
* @returns A Promise that resolves to the video item container ID
* @throws Will throw an error if the API request fails
*/
async function createVideoItemContainer(
request: ThreadsPostRequest
): Promise<string> {
const url = `${THREADS_API_BASE_URL}/${request.userId}/threads`;
const body = new URLSearchParams({
access_token: request.accessToken,
is_carousel_item: "true",
media_type: "VIDEO",
video_url: request.videoUrl!,
...(request.altText && { alt_text: request.altText }),
});

const response = await fetch(url, {
method: "POST",
body: body,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});

const responseText = await response.text();

if (!response.ok) {
throw new Error(
`Failed to create video item container: ${response.statusText}. Details: ${responseText}`
);
}

try {
const data = JSON.parse(responseText);
return data.id;
} catch (error) {
console.error(`Failed to parse response JSON: ${error}`);
throw new Error(`Invalid response from Threads API: ${responseText}`);
}
}

/**
* Creates a carousel item for a Threads carousel post.
*
Expand Down Expand Up @@ -272,52 +317,24 @@ async function checkContainerStatus(
*
* @param userId - The user ID of the Threads account
* @param accessToken - The access token for authentication
* @param containerId - The ID of the container to publish (as a number)
* @param mediaType - The type of media being published
* @param containerId - The ID of the container to publish
* @returns A Promise that resolves to the published container ID
* @throws Will throw an error if the API request fails, if publishing times out, or if the container is not ready (for videos)
* @throws Will throw an error if the API request fails or if publishing times out
*
* @example
* ```typescript
* const { containerId, mediaType } = await createThreadsContainer(request);
* const publishedId = await publishThreadsContainer("123456", "your_access_token", parseInt(containerId, 10), mediaType);
* const publishedId = await publishThreadsContainer("123456", "your_access_token", "container_id");
* ```
*/
export async function publishThreadsContainer(
userId: string,
accessToken: string,
containerId: number,
mediaType: string
): Promise<number> {
if (mediaType === "VIDEO") {
// Check container status before publishing for videos
let status = await checkContainerStatus(
containerId.toString(),
accessToken
);
let attempts = 0;
const maxAttempts = 10; // Increased from 5 to 10 for videos

while (status !== "FINISHED" && attempts < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, 30000)); // Wait for 30 seconds
status = await checkContainerStatus(containerId.toString(), accessToken);
attempts++;
console.log(
`Video container status check attempt ${attempts}: ${status}`
);
}

if (status !== "FINISHED") {
throw new Error(
`Video container not ready after ${maxAttempts} attempts. Current status: ${status}`
);
}
}

containerId: string
): Promise<string> {
const publishUrl = `${THREADS_API_BASE_URL}/${userId}/threads_publish`;
const publishBody = new URLSearchParams({
access_token: accessToken,
creation_id: containerId.toString(), // Convert to string for URLSearchParams, but it will be sent as a number
creation_id: containerId,
});

const publishResponse = await fetch(publishUrl, {
Expand All @@ -328,43 +345,35 @@ export async function publishThreadsContainer(
},
});

const responseText = await publishResponse.text();

if (!publishResponse.ok) {
console.error(`Publish response body: ${responseText}`);
throw new Error(
`Failed to publish Threads container: ${publishResponse.statusText}. Details: ${responseText}`
`Failed to publish Threads container: ${publishResponse.statusText}`
);
}

// For non-video posts, we still need to check if the post was actually published
if (mediaType !== "VIDEO") {
let status = await checkContainerStatus(
containerId.toString(),
accessToken
);
let attempts = 0;
const maxAttempts = 5;

while (
status !== "PUBLISHED" &&
status !== "FINISHED" &&
attempts < maxAttempts
) {
await new Promise((resolve) => setTimeout(resolve, 60000)); // Wait for 1 minute
status = await checkContainerStatus(containerId.toString(), accessToken);
attempts++;
}
// Check container status
let status = await checkContainerStatus(containerId, accessToken);
let attempts = 0;
const maxAttempts = 5;

if (status === "ERROR") {
throw new Error(`Failed to publish container. Error: ${status}`);
}
while (
status !== "PUBLISHED" &&
status !== "FINISHED" &&
attempts < maxAttempts
) {
await new Promise((resolve) => setTimeout(resolve, 60000)); // Wait for 1 minute
status = await checkContainerStatus(containerId, accessToken);
attempts++;
}

if (status !== "PUBLISHED" && status !== "FINISHED") {
throw new Error(
`Container not published after ${maxAttempts} attempts. Current status: ${status}`
);
}
if (status === "ERROR") {
throw new Error(`Failed to publish container. Error: ${status}`);
}

if (status !== "PUBLISHED" && status !== "FINISHED") {
throw new Error(
`Container not published after ${maxAttempts} attempts. Current status: ${status}`
);
}

return containerId; // Return the container ID as the published ID
Expand Down Expand Up @@ -408,24 +417,18 @@ export function serveRequests() {
}

// Create the Threads container
const { containerId, mediaType } = await createThreadsContainer(
requestData
);
const containerId = await createThreadsContainer(requestData);

// Attempt to publish the Threads container
// Immediately attempt to publish the Threads container
const publishedId = await publishThreadsContainer(
requestData.userId,
requestData.accessToken,
containerId,
mediaType
containerId
);

return new Response(
JSON.stringify({ success: true, publishedId: publishedId.toString() }),
{
headers: { "Content-Type": "application/json" },
}
);
return new Response(JSON.stringify({ success: true, publishedId }), {
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Error posting to Threads:", error);
return new Response(
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@codybrom/denim",
"version": "1.3.3",
"version": "1.3.4",
"description": "Typescript/Deno module to simplify posting to Threads",
"main": "mod.ts",
"directories": {
Expand Down

0 comments on commit 4d0561f

Please sign in to comment.