@codybrom/denim 1.3.6 → 2.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.
Files changed (46) hide show
  1. package/.github/workflows/publish.yml +17 -7
  2. package/.vscode/settings.json +34 -9
  3. package/CHANGELOG.md +128 -0
  4. package/deno.json +22 -8
  5. package/deno.lock +17 -59
  6. package/examples/edge-function.ts +171 -177
  7. package/mod.ts +138 -635
  8. package/mod_test.ts +1287 -431
  9. package/package.json +22 -22
  10. package/readme.md +155 -191
  11. package/src/api/createCarouselItem.ts +86 -0
  12. package/src/api/createThreadsContainer.ts +122 -0
  13. package/src/api/debugToken.ts +35 -0
  14. package/src/api/deleteThread.ts +36 -0
  15. package/src/api/exchangeCodeForToken.ts +50 -0
  16. package/src/api/exchangeToken.ts +36 -0
  17. package/src/api/getAppAccessToken.ts +35 -0
  18. package/src/api/getConversation.ts +51 -0
  19. package/src/api/getGhostPosts.ts +50 -0
  20. package/src/api/getLocation.ts +38 -0
  21. package/src/api/getMediaInsights.ts +39 -0
  22. package/src/api/getMentions.ts +57 -0
  23. package/src/api/getOEmbed.ts +41 -0
  24. package/src/api/getProfile.ts +46 -0
  25. package/src/api/getProfilePosts.ts +53 -0
  26. package/src/api/getPublishingLimit.ts +59 -0
  27. package/src/api/getReplies.ts +51 -0
  28. package/src/api/getSingleThread.ts +37 -0
  29. package/src/api/getThreadsList.ts +49 -0
  30. package/src/api/getUserInsights.ts +54 -0
  31. package/src/api/getUserReplies.ts +54 -0
  32. package/src/api/lookupProfile.ts +53 -0
  33. package/src/api/manageReply.ts +41 -0
  34. package/src/api/publishThreadsContainer.ts +107 -0
  35. package/src/api/refreshToken.ts +33 -0
  36. package/src/api/repost.ts +38 -0
  37. package/src/api/searchKeyword.ts +86 -0
  38. package/src/api/searchLocations.ts +46 -0
  39. package/src/constants.ts +80 -0
  40. package/src/types.ts +925 -0
  41. package/src/utils/checkContainerStatus.ts +39 -0
  42. package/src/utils/getAPI.ts +13 -0
  43. package/src/utils/mock_threads_api.ts +582 -0
  44. package/src/utils/validateRequest.ts +166 -0
  45. package/mock_threads_api.ts +0 -174
  46. package/types.ts +0 -235
@@ -0,0 +1,35 @@
1
+ import { THREADS_API_BASE_URL } from "../constants.ts";
2
+ import type { DebugTokenInfo } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Retrieves debug information about an access token.
7
+ *
8
+ * @param accessToken - The access token for authentication
9
+ * @param inputToken - The token to debug
10
+ * @returns A Promise that resolves to the DebugTokenInfo
11
+ * @throws Will throw an error if the API request fails
12
+ */
13
+ export async function debugToken(
14
+ accessToken: string,
15
+ inputToken: string,
16
+ ): Promise<DebugTokenInfo> {
17
+ const api = getAPI();
18
+ if (api) {
19
+ return api.debugToken(accessToken, inputToken);
20
+ }
21
+
22
+ const url = new URL(`${THREADS_API_BASE_URL}/debug_token`);
23
+ url.searchParams.append("input_token", inputToken);
24
+ url.searchParams.append("access_token", accessToken);
25
+
26
+ const response = await fetch(url.toString());
27
+ if (!response.ok) {
28
+ const errorBody = await response.text();
29
+ throw new Error(
30
+ `Failed to debug token (${response.status}): ${errorBody}`,
31
+ );
32
+ }
33
+
34
+ return await response.json();
35
+ }
@@ -0,0 +1,36 @@
1
+ import { THREADS_API_BASE_URL } from "../constants.ts";
2
+ import { getAPI } from "../utils/getAPI.ts";
3
+
4
+ /**
5
+ * Deletes a Threads post.
6
+ *
7
+ * @param mediaId - The ID of the Threads media to delete
8
+ * @param accessToken - The access token for authentication
9
+ * @returns A Promise that resolves to an object indicating success
10
+ * @throws Will throw an error if the API request fails
11
+ */
12
+ export async function deleteThread(
13
+ mediaId: string,
14
+ accessToken: string,
15
+ ): Promise<{ success: boolean; deleted_id?: string }> {
16
+ const api = getAPI();
17
+ if (api) {
18
+ return api.deleteThread(mediaId, accessToken);
19
+ }
20
+
21
+ const url = new URL(`${THREADS_API_BASE_URL}/${mediaId}`);
22
+ url.searchParams.append("access_token", accessToken);
23
+
24
+ const response = await fetch(url.toString(), {
25
+ method: "DELETE",
26
+ });
27
+
28
+ if (!response.ok) {
29
+ const responseText = await response.text();
30
+ throw new Error(
31
+ `Failed to delete thread (${response.status}): ${responseText}`,
32
+ );
33
+ }
34
+
35
+ return await response.json();
36
+ }
@@ -0,0 +1,50 @@
1
+ import { THREADS_OAUTH_BASE_URL } from "../constants.ts";
2
+ import type { AuthCodeResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Exchanges an OAuth authorization code for a short-lived access token.
7
+ * This is the first step in the OAuth flow after the user authorizes your app.
8
+ *
9
+ * @param clientId - Your Threads App ID
10
+ * @param clientSecret - Your Threads App Secret
11
+ * @param code - The authorization code received from the redirect
12
+ * @param redirectUri - The redirect URI used in the authorization request
13
+ * @returns A Promise that resolves to the access token and user ID
14
+ * @throws Will throw an error if the API request fails
15
+ */
16
+ export async function exchangeCodeForToken(
17
+ clientId: string,
18
+ clientSecret: string,
19
+ code: string,
20
+ redirectUri: string,
21
+ ): Promise<AuthCodeResponse> {
22
+ const api = getAPI();
23
+ if (api) {
24
+ return api.exchangeCodeForToken(clientId, clientSecret, code, redirectUri);
25
+ }
26
+
27
+ const url = `${THREADS_OAUTH_BASE_URL}/oauth/access_token`;
28
+ const body = new URLSearchParams({
29
+ client_id: clientId,
30
+ client_secret: clientSecret,
31
+ code: code,
32
+ grant_type: "authorization_code",
33
+ redirect_uri: redirectUri,
34
+ });
35
+
36
+ const response = await fetch(url, {
37
+ method: "POST",
38
+ body: body,
39
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
40
+ });
41
+
42
+ if (!response.ok) {
43
+ const responseText = await response.text();
44
+ throw new Error(
45
+ `Failed to exchange authorization code: ${responseText}`,
46
+ );
47
+ }
48
+
49
+ return await response.json();
50
+ }
@@ -0,0 +1,36 @@
1
+ import { THREADS_OAUTH_BASE_URL } from "../constants.ts";
2
+ import type { TokenResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Exchanges a short-lived access token for a long-lived one.
7
+ *
8
+ * @param clientSecret - The app's client secret
9
+ * @param accessToken - The short-lived access token to exchange
10
+ * @returns A Promise that resolves to the TokenResponse with the long-lived token
11
+ * @throws Will throw an error if the API request fails
12
+ */
13
+ export async function exchangeToken(
14
+ clientSecret: string,
15
+ accessToken: string,
16
+ ): Promise<TokenResponse> {
17
+ const api = getAPI();
18
+ if (api) {
19
+ return api.exchangeToken(clientSecret, accessToken);
20
+ }
21
+
22
+ const url = new URL(`${THREADS_OAUTH_BASE_URL}/access_token`);
23
+ url.searchParams.append("grant_type", "th_exchange_token");
24
+ url.searchParams.append("client_secret", clientSecret);
25
+ url.searchParams.append("access_token", accessToken);
26
+
27
+ const response = await fetch(url.toString());
28
+ if (!response.ok) {
29
+ const errorBody = await response.text();
30
+ throw new Error(
31
+ `Failed to exchange token (${response.status}): ${errorBody}`,
32
+ );
33
+ }
34
+
35
+ return await response.json();
36
+ }
@@ -0,0 +1,35 @@
1
+ import { THREADS_OAUTH_BASE_URL } from "../constants.ts";
2
+ import type { TokenResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Gets an app-level access token using client credentials.
7
+ * App tokens are used for server-to-server requests that don't act on behalf of a user.
8
+ *
9
+ * @param clientId - Your Threads App ID
10
+ * @param clientSecret - Your Threads App Secret
11
+ * @returns A Promise that resolves to the app access token
12
+ * @throws Will throw an error if the API request fails
13
+ */
14
+ export async function getAppAccessToken(
15
+ clientId: string,
16
+ clientSecret: string,
17
+ ): Promise<TokenResponse> {
18
+ const api = getAPI();
19
+ if (api) {
20
+ return api.getAppAccessToken(clientId, clientSecret);
21
+ }
22
+
23
+ const url = new URL(`${THREADS_OAUTH_BASE_URL}/oauth/access_token`);
24
+ url.searchParams.append("grant_type", "client_credentials");
25
+ url.searchParams.append("client_id", clientId);
26
+ url.searchParams.append("client_secret", clientSecret);
27
+
28
+ const response = await fetch(url.toString());
29
+ if (!response.ok) {
30
+ const responseText = await response.text();
31
+ throw new Error(`Failed to get app access token: ${responseText}`);
32
+ }
33
+
34
+ return await response.json();
35
+ }
@@ -0,0 +1,51 @@
1
+ import { REPLY_FIELDS, THREADS_API_BASE_URL } from "../constants.ts";
2
+ import type { CursorPaginationOptions, ThreadsListResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Retrieves the full conversation thread for a Threads media object.
7
+ *
8
+ * @param mediaId - The ID of the root Threads media object
9
+ * @param accessToken - The access token for authentication
10
+ * @param options - Optional pagination parameters
11
+ * @param fields - Optional array of fields to return
12
+ * @param reverse - Optional boolean to sort in reverse chronological order (default: true)
13
+ * @returns A Promise that resolves to the ThreadsListResponse
14
+ * @throws Will throw an error if the API request fails
15
+ */
16
+ export async function getConversation(
17
+ mediaId: string,
18
+ accessToken: string,
19
+ options?: CursorPaginationOptions,
20
+ fields?: string[],
21
+ reverse?: boolean,
22
+ ): Promise<ThreadsListResponse> {
23
+ const api = getAPI();
24
+ if (api) {
25
+ return api.getConversation(mediaId, accessToken, options, fields, reverse);
26
+ }
27
+
28
+ const fieldList = (fields ?? REPLY_FIELDS).join(",");
29
+ const url = new URL(`${THREADS_API_BASE_URL}/${mediaId}/conversation`);
30
+ url.searchParams.append("fields", fieldList);
31
+ url.searchParams.append("access_token", accessToken);
32
+
33
+ if (reverse !== undefined) {
34
+ url.searchParams.append("reverse", String(reverse));
35
+ }
36
+
37
+ if (options) {
38
+ if (options.after) url.searchParams.append("after", options.after);
39
+ if (options.before) url.searchParams.append("before", options.before);
40
+ }
41
+
42
+ const response = await fetch(url.toString());
43
+ if (!response.ok) {
44
+ const errorBody = await response.text();
45
+ throw new Error(
46
+ `Failed to get conversation (${response.status}): ${errorBody}`,
47
+ );
48
+ }
49
+
50
+ return await response.json();
51
+ }
@@ -0,0 +1,50 @@
1
+ import { THREADS_API_BASE_URL, USER_THREADS_FIELDS } from "../constants.ts";
2
+ import type { PaginationOptions, ThreadsListResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Retrieves a list of ghost posts created by a user.
7
+ *
8
+ * @param userId - The user ID of the Threads account
9
+ * @param accessToken - The access token for authentication
10
+ * @param options - Optional pagination parameters
11
+ * @param fields - Optional array of fields to return
12
+ * @returns A Promise that resolves to the ThreadsListResponse
13
+ * @throws Will throw an error if the API request fails
14
+ */
15
+ export async function getGhostPosts(
16
+ userId: string,
17
+ accessToken: string,
18
+ options?: PaginationOptions,
19
+ fields?: string[],
20
+ ): Promise<ThreadsListResponse> {
21
+ const api = getAPI();
22
+ if (api) {
23
+ return api.getGhostPosts(userId, accessToken, options, fields);
24
+ }
25
+
26
+ const fieldList = (fields ?? USER_THREADS_FIELDS).join(",");
27
+ const url = new URL(`${THREADS_API_BASE_URL}/${userId}/ghost_posts`);
28
+ url.searchParams.append("fields", fieldList);
29
+ url.searchParams.append("access_token", accessToken);
30
+
31
+ if (options) {
32
+ if (options.since) url.searchParams.append("since", String(options.since));
33
+ if (options.until) url.searchParams.append("until", String(options.until));
34
+ if (options.limit) {
35
+ url.searchParams.append("limit", options.limit.toString());
36
+ }
37
+ if (options.after) url.searchParams.append("after", options.after);
38
+ if (options.before) url.searchParams.append("before", options.before);
39
+ }
40
+
41
+ const response = await fetch(url.toString());
42
+ if (!response.ok) {
43
+ const errorBody = await response.text();
44
+ throw new Error(
45
+ `Failed to get ghost posts (${response.status}): ${errorBody}`,
46
+ );
47
+ }
48
+
49
+ return await response.json();
50
+ }
@@ -0,0 +1,38 @@
1
+ import { LOCATION_FIELDS, THREADS_API_BASE_URL } from "../constants.ts";
2
+ import type { ThreadsLocation } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Retrieves information about a specific location.
7
+ *
8
+ * @param locationId - The ID of the location
9
+ * @param accessToken - The access token for authentication
10
+ * @param fields - Optional array of fields to return
11
+ * @returns A Promise that resolves to the ThreadsLocation object
12
+ * @throws Will throw an error if the API request fails
13
+ */
14
+ export async function getLocation(
15
+ locationId: string,
16
+ accessToken: string,
17
+ fields?: string[],
18
+ ): Promise<ThreadsLocation> {
19
+ const api = getAPI();
20
+ if (api) {
21
+ return api.getLocation(locationId, accessToken, fields);
22
+ }
23
+
24
+ const fieldList = (fields ?? LOCATION_FIELDS).join(",");
25
+ const url = new URL(`${THREADS_API_BASE_URL}/${locationId}`);
26
+ url.searchParams.append("fields", fieldList);
27
+ url.searchParams.append("access_token", accessToken);
28
+
29
+ const response = await fetch(url.toString());
30
+ if (!response.ok) {
31
+ const errorBody = await response.text();
32
+ throw new Error(
33
+ `Failed to get location (${response.status}): ${errorBody}`,
34
+ );
35
+ }
36
+
37
+ return await response.json();
38
+ }
@@ -0,0 +1,39 @@
1
+ import { THREADS_API_BASE_URL } from "../constants.ts";
2
+ import type { MediaInsightsResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Retrieves insight metrics for a specific Threads media object.
7
+ *
8
+ * Available metrics: views, likes, replies, reposts, quotes, shares
9
+ *
10
+ * @param mediaId - The ID of the Threads media object
11
+ * @param accessToken - The access token for authentication
12
+ * @param metrics - Array of metric names to retrieve
13
+ * @returns A Promise that resolves to the MediaInsightsResponse
14
+ * @throws Will throw an error if the API request fails
15
+ */
16
+ export async function getMediaInsights(
17
+ mediaId: string,
18
+ accessToken: string,
19
+ metrics: string[],
20
+ ): Promise<MediaInsightsResponse> {
21
+ const api = getAPI();
22
+ if (api) {
23
+ return api.getMediaInsights(mediaId, accessToken, metrics);
24
+ }
25
+
26
+ const url = new URL(`${THREADS_API_BASE_URL}/${mediaId}/insights`);
27
+ url.searchParams.append("metric", metrics.join(","));
28
+ url.searchParams.append("access_token", accessToken);
29
+
30
+ const response = await fetch(url.toString());
31
+ if (!response.ok) {
32
+ const errorBody = await response.text();
33
+ throw new Error(
34
+ `Failed to get media insights (${response.status}): ${errorBody}`,
35
+ );
36
+ }
37
+
38
+ return await response.json();
39
+ }
@@ -0,0 +1,57 @@
1
+ import { REPLY_FIELDS, THREADS_API_BASE_URL } from "../constants.ts";
2
+ import type { PaginationOptions, ThreadsListResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ const DEFAULT_FIELDS = [
6
+ ...REPLY_FIELDS,
7
+ "alt_text",
8
+ "link_attachment_url",
9
+ "poll_attachment",
10
+ ];
11
+
12
+ /**
13
+ * Retrieves a list of posts where the user is mentioned.
14
+ *
15
+ * @param userId - The user ID of the Threads account
16
+ * @param accessToken - The access token for authentication
17
+ * @param options - Optional pagination parameters
18
+ * @param fields - Optional array of fields to return
19
+ * @returns A Promise that resolves to the ThreadsListResponse
20
+ * @throws Will throw an error if the API request fails
21
+ */
22
+ export async function getMentions(
23
+ userId: string,
24
+ accessToken: string,
25
+ options?: PaginationOptions,
26
+ fields?: string[],
27
+ ): Promise<ThreadsListResponse> {
28
+ const api = getAPI();
29
+ if (api) {
30
+ return api.getMentions(userId, accessToken, options, fields);
31
+ }
32
+
33
+ const fieldList = (fields ?? DEFAULT_FIELDS).join(",");
34
+ const url = new URL(`${THREADS_API_BASE_URL}/${userId}/mentions`);
35
+ url.searchParams.append("fields", fieldList);
36
+ url.searchParams.append("access_token", accessToken);
37
+
38
+ if (options) {
39
+ if (options.since) url.searchParams.append("since", String(options.since));
40
+ if (options.until) url.searchParams.append("until", String(options.until));
41
+ if (options.limit) {
42
+ url.searchParams.append("limit", options.limit.toString());
43
+ }
44
+ if (options.after) url.searchParams.append("after", options.after);
45
+ if (options.before) url.searchParams.append("before", options.before);
46
+ }
47
+
48
+ const response = await fetch(url.toString());
49
+ if (!response.ok) {
50
+ const errorBody = await response.text();
51
+ throw new Error(
52
+ `Failed to get mentions (${response.status}): ${errorBody}`,
53
+ );
54
+ }
55
+
56
+ return await response.json();
57
+ }
@@ -0,0 +1,41 @@
1
+ import { THREADS_API_BASE_URL } from "../constants.ts";
2
+ import type { OEmbedResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Retrieves oEmbed HTML for a Threads post.
7
+ *
8
+ * @param accessToken - The access token for authentication
9
+ * @param postUrl - The URL of the Threads post to embed
10
+ * @param maxWidth - Optional maximum width of the embed in pixels
11
+ * @returns A Promise that resolves to the OEmbedResponse
12
+ * @throws Will throw an error if the API request fails
13
+ */
14
+ export async function getOEmbed(
15
+ accessToken: string,
16
+ postUrl: string,
17
+ maxWidth?: number,
18
+ ): Promise<OEmbedResponse> {
19
+ const api = getAPI();
20
+ if (api) {
21
+ return api.getOEmbed(accessToken, postUrl, maxWidth);
22
+ }
23
+
24
+ const url = new URL(`${THREADS_API_BASE_URL}/oembed`);
25
+ url.searchParams.append("url", postUrl);
26
+ url.searchParams.append("access_token", accessToken);
27
+
28
+ if (maxWidth !== undefined) {
29
+ url.searchParams.append("maxwidth", maxWidth.toString());
30
+ }
31
+
32
+ const response = await fetch(url.toString());
33
+ if (!response.ok) {
34
+ const errorBody = await response.text();
35
+ throw new Error(
36
+ `Failed to get oEmbed (${response.status}): ${errorBody}`,
37
+ );
38
+ }
39
+
40
+ return await response.json();
41
+ }
@@ -0,0 +1,46 @@
1
+ import { THREADS_API_BASE_URL } from "../constants.ts";
2
+ import type { ThreadsProfile } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ const DEFAULT_FIELDS = [
6
+ "id",
7
+ "username",
8
+ "name",
9
+ "threads_profile_picture_url",
10
+ "threads_biography",
11
+ "is_verified",
12
+ "recently_searched_keywords",
13
+ ];
14
+
15
+ /**
16
+ * Retrieves profile information for a Threads user.
17
+ *
18
+ * @param userId - The user ID (or "me" for the authenticated user)
19
+ * @param accessToken - The access token for authentication
20
+ * @param fields - Optional array of fields to return
21
+ * @returns A Promise that resolves to the ThreadsProfile object
22
+ * @throws Will throw an error if the API request fails
23
+ */
24
+ export async function getProfile(
25
+ userId: string,
26
+ accessToken: string,
27
+ fields?: string[],
28
+ ): Promise<ThreadsProfile> {
29
+ const api = getAPI();
30
+ if (api) {
31
+ return api.getProfile(userId, accessToken, fields);
32
+ }
33
+
34
+ const fieldList = (fields ?? DEFAULT_FIELDS).join(",");
35
+ const url = new URL(`${THREADS_API_BASE_URL}/${userId}`);
36
+ url.searchParams.append("fields", fieldList);
37
+ url.searchParams.append("access_token", accessToken);
38
+
39
+ const response = await fetch(url.toString());
40
+ if (!response.ok) {
41
+ const errorBody = await response.text();
42
+ throw new Error(`Failed to get profile (${response.status}): ${errorBody}`);
43
+ }
44
+
45
+ return await response.json();
46
+ }
@@ -0,0 +1,53 @@
1
+ import { THREADS_API_BASE_URL, USER_THREADS_FIELDS } from "../constants.ts";
2
+ import type { PaginationOptions, ThreadsListResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ const DEFAULT_FIELDS = USER_THREADS_FIELDS.filter((f) => f !== "owner");
6
+
7
+ /**
8
+ * Retrieves posts from a public Threads profile by username.
9
+ *
10
+ * @param accessToken - The access token for authentication
11
+ * @param username - The exact username whose posts to retrieve
12
+ * @param options - Optional pagination parameters
13
+ * @param fields - Optional array of fields to return
14
+ * @returns A Promise that resolves to the ThreadsListResponse
15
+ * @throws Will throw an error if the API request fails
16
+ */
17
+ export async function getProfilePosts(
18
+ accessToken: string,
19
+ username: string,
20
+ options?: PaginationOptions,
21
+ fields?: string[],
22
+ ): Promise<ThreadsListResponse> {
23
+ const api = getAPI();
24
+ if (api) {
25
+ return api.getProfilePosts(accessToken, username, options, fields);
26
+ }
27
+
28
+ const fieldList = (fields ?? DEFAULT_FIELDS).join(",");
29
+ const url = new URL(`${THREADS_API_BASE_URL}/profile_posts`);
30
+ url.searchParams.append("username", username);
31
+ url.searchParams.append("fields", fieldList);
32
+ url.searchParams.append("access_token", accessToken);
33
+
34
+ if (options) {
35
+ if (options.since) url.searchParams.append("since", String(options.since));
36
+ if (options.until) url.searchParams.append("until", String(options.until));
37
+ if (options.limit) {
38
+ url.searchParams.append("limit", options.limit.toString());
39
+ }
40
+ if (options.after) url.searchParams.append("after", options.after);
41
+ if (options.before) url.searchParams.append("before", options.before);
42
+ }
43
+
44
+ const response = await fetch(url.toString());
45
+ if (!response.ok) {
46
+ const errorBody = await response.text();
47
+ throw new Error(
48
+ `Failed to get profile posts (${response.status}): ${errorBody}`,
49
+ );
50
+ }
51
+
52
+ return await response.json();
53
+ }
@@ -0,0 +1,59 @@
1
+ import { THREADS_API_BASE_URL } from "../constants.ts";
2
+ import type { PublishingLimit } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ const DEFAULT_FIELDS = [
6
+ "quota_usage",
7
+ "config",
8
+ "reply_quota_usage",
9
+ "reply_config",
10
+ "delete_quota_usage",
11
+ "delete_config",
12
+ "location_search_quota_usage",
13
+ "location_search_config",
14
+ ];
15
+
16
+ /**
17
+ * Retrieves the current publishing rate limit usage for a user.
18
+ *
19
+ * @param userId - The user ID of the Threads account
20
+ * @param accessToken - The access token for authentication
21
+ * @param fields - Optional array of fields to return (defaults to all quota fields)
22
+ * @returns A Promise that resolves to the rate limit usage information
23
+ * @throws Will throw an error if the API request fails
24
+ * @example
25
+ * ```typescript
26
+ * const rateLimit = await getPublishingLimit("123456", "your_access_token");
27
+ * console.log(`Current usage: ${rateLimit.quota_usage}`);
28
+ * ```
29
+ */
30
+ export async function getPublishingLimit(
31
+ userId: string,
32
+ accessToken: string,
33
+ fields?: string[],
34
+ ): Promise<PublishingLimit> {
35
+ const api = getAPI();
36
+ if (api) {
37
+ return api.getPublishingLimit(userId, accessToken, fields);
38
+ }
39
+ const fieldList = (fields ?? DEFAULT_FIELDS).join(",");
40
+ const url = new URL(
41
+ `${THREADS_API_BASE_URL}/${userId}/threads_publishing_limit`,
42
+ );
43
+ url.searchParams.append("access_token", accessToken);
44
+ url.searchParams.append("fields", fieldList);
45
+
46
+ const response = await fetch(url.toString());
47
+ if (!response.ok) {
48
+ const errorBody = await response.text();
49
+ throw new Error(
50
+ `Failed to get publishing limit (${response.status}): ${errorBody}`,
51
+ );
52
+ }
53
+
54
+ const data = await response.json();
55
+ if (!data.data?.[0]) {
56
+ throw new Error("No publishing limit data returned");
57
+ }
58
+ return data.data[0];
59
+ }
@@ -0,0 +1,51 @@
1
+ import { REPLY_FIELDS, THREADS_API_BASE_URL } from "../constants.ts";
2
+ import type { CursorPaginationOptions, ThreadsListResponse } from "../types.ts";
3
+ import { getAPI } from "../utils/getAPI.ts";
4
+
5
+ /**
6
+ * Retrieves replies to a specific Threads media object.
7
+ *
8
+ * @param mediaId - The ID of the Threads media object
9
+ * @param accessToken - The access token for authentication
10
+ * @param options - Optional pagination parameters
11
+ * @param fields - Optional array of fields to return
12
+ * @param reverse - Optional boolean to sort in reverse chronological order (default: true)
13
+ * @returns A Promise that resolves to the ThreadsListResponse
14
+ * @throws Will throw an error if the API request fails
15
+ */
16
+ export async function getReplies(
17
+ mediaId: string,
18
+ accessToken: string,
19
+ options?: CursorPaginationOptions,
20
+ fields?: string[],
21
+ reverse?: boolean,
22
+ ): Promise<ThreadsListResponse> {
23
+ const api = getAPI();
24
+ if (api) {
25
+ return api.getReplies(mediaId, accessToken, options, fields, reverse);
26
+ }
27
+
28
+ const fieldList = (fields ?? REPLY_FIELDS).join(",");
29
+ const url = new URL(`${THREADS_API_BASE_URL}/${mediaId}/replies`);
30
+ url.searchParams.append("fields", fieldList);
31
+ url.searchParams.append("access_token", accessToken);
32
+
33
+ if (reverse !== undefined) {
34
+ url.searchParams.append("reverse", String(reverse));
35
+ }
36
+
37
+ if (options) {
38
+ if (options.after) url.searchParams.append("after", options.after);
39
+ if (options.before) url.searchParams.append("before", options.before);
40
+ }
41
+
42
+ const response = await fetch(url.toString());
43
+ if (!response.ok) {
44
+ const errorBody = await response.text();
45
+ throw new Error(
46
+ `Failed to get replies (${response.status}): ${errorBody}`,
47
+ );
48
+ }
49
+
50
+ return await response.json();
51
+ }