naystack 1.8.12 → 1.8.14

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 (74) hide show
  1. package/README.md +189 -31
  2. package/dist/auth/index.cjs.js +158 -18
  3. package/dist/auth/index.d.mts +3 -1
  4. package/dist/auth/index.d.ts +3 -1
  5. package/dist/auth/index.esm.js +158 -18
  6. package/dist/auth/instagram/index.cjs.js +158 -18
  7. package/dist/auth/instagram/index.d.mts +6 -1
  8. package/dist/auth/instagram/index.d.ts +6 -1
  9. package/dist/auth/instagram/index.esm.js +158 -18
  10. package/dist/auth/instagram/route.cjs.js +158 -18
  11. package/dist/auth/instagram/route.d.mts +3 -1
  12. package/dist/auth/instagram/route.d.ts +3 -1
  13. package/dist/auth/instagram/route.esm.js +158 -18
  14. package/dist/auth/instagram/utils.cjs.js +2 -2
  15. package/dist/auth/instagram/utils.d.mts +13 -2
  16. package/dist/auth/instagram/utils.d.ts +13 -2
  17. package/dist/auth/instagram/utils.esm.js +2 -2
  18. package/dist/file/setup.d.mts +13 -5
  19. package/dist/file/setup.d.ts +13 -5
  20. package/dist/index.d.mts +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/socials/index.cjs.js +298 -72
  23. package/dist/socials/index.d.mts +6 -4
  24. package/dist/socials/index.d.ts +6 -4
  25. package/dist/socials/index.esm.js +296 -72
  26. package/dist/socials/instagram/getters.cjs.js +76 -25
  27. package/dist/socials/instagram/getters.d.mts +56 -6
  28. package/dist/socials/instagram/getters.d.ts +56 -6
  29. package/dist/socials/instagram/getters.esm.js +74 -25
  30. package/dist/socials/instagram/setters.cjs.js +192 -14
  31. package/dist/socials/instagram/setters.d.mts +43 -3
  32. package/dist/socials/instagram/setters.d.ts +43 -3
  33. package/dist/socials/instagram/setters.esm.js +191 -14
  34. package/dist/socials/instagram/types.d.mts +48 -11
  35. package/dist/socials/instagram/types.d.ts +48 -11
  36. package/dist/socials/instagram/utils.cjs.js +28 -15
  37. package/dist/socials/instagram/utils.d.mts +7 -18
  38. package/dist/socials/instagram/utils.d.ts +7 -18
  39. package/dist/socials/instagram/utils.esm.js +26 -14
  40. package/dist/socials/instagram/webhook.cjs.js +1 -1
  41. package/dist/socials/instagram/webhook.esm.js +1 -1
  42. package/dist/socials/meta/container.cjs.js +82 -0
  43. package/dist/socials/meta/container.d.mts +38 -0
  44. package/dist/socials/meta/container.d.ts +38 -0
  45. package/dist/socials/meta/container.esm.js +57 -0
  46. package/dist/socials/meta/request.cjs.js +60 -0
  47. package/dist/socials/meta/request.d.mts +50 -0
  48. package/dist/socials/meta/request.d.ts +50 -0
  49. package/dist/socials/meta/request.esm.js +34 -0
  50. package/dist/socials/meta/types.cjs.js +34 -0
  51. package/dist/socials/meta/types.d.mts +51 -0
  52. package/dist/socials/meta/types.d.ts +51 -0
  53. package/dist/socials/meta/types.esm.js +9 -0
  54. package/dist/socials/{meta-webhook.cjs.js → meta/webhook.cjs.js} +4 -4
  55. package/dist/socials/{meta-webhook.esm.js → meta/webhook.esm.js} +1 -1
  56. package/dist/socials/threads/getters.cjs.js +36 -11
  57. package/dist/socials/threads/getters.d.mts +23 -2
  58. package/dist/socials/threads/getters.d.ts +23 -2
  59. package/dist/socials/threads/getters.esm.js +35 -11
  60. package/dist/socials/threads/setters.cjs.js +150 -44
  61. package/dist/socials/threads/setters.d.mts +25 -34
  62. package/dist/socials/threads/setters.d.ts +25 -34
  63. package/dist/socials/threads/setters.esm.js +149 -41
  64. package/dist/socials/threads/types.d.mts +40 -1
  65. package/dist/socials/threads/types.d.ts +40 -1
  66. package/dist/socials/threads/utils.cjs.js +29 -11
  67. package/dist/socials/threads/utils.d.mts +8 -17
  68. package/dist/socials/threads/utils.d.ts +8 -17
  69. package/dist/socials/threads/utils.esm.js +27 -10
  70. package/dist/socials/threads/webhook.cjs.js +1 -1
  71. package/dist/socials/threads/webhook.esm.js +1 -1
  72. package/package.json +1 -1
  73. /package/dist/socials/{meta-webhook.d.mts → meta/webhook.d.mts} +0 -0
  74. /package/dist/socials/{meta-webhook.d.ts → meta/webhook.d.ts} +0 -0
@@ -1,31 +1,55 @@
1
- // src/socials/threads/utils.ts
2
- function getThreadsURL(token, path, params) {
3
- return `https://graph.threads.net/v1.0/${path}?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}&access_token=${token}`;
4
- }
5
- function getThreadsData(token, path, params, method) {
6
- return fetch(getThreadsURL(token, path, params), {
7
- method: method || "GET"
8
- }).then((res) => res.json());
1
+ // src/socials/meta/request.ts
2
+ function createGraphClient(baseURL) {
3
+ const buildURL = (token, path, params = {}) => {
4
+ const search = new URLSearchParams();
5
+ for (const [key, value] of Object.entries(params)) {
6
+ if (value !== void 0) search.set(key, String(value));
7
+ }
8
+ search.set("access_token", token);
9
+ return `${baseURL}/${path}?${search.toString()}`;
10
+ };
11
+ const request = async (token, path, options = {}) => {
12
+ const response = await fetch(buildURL(token, path, options.params), {
13
+ method: options.method || (options.body ? "POST" : "GET"),
14
+ headers: {
15
+ Authorization: `Bearer ${token}`,
16
+ ...options.body ? { "Content-Type": "application/json" } : {}
17
+ },
18
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
19
+ });
20
+ return response.json().catch(() => null);
21
+ };
22
+ return { buildURL, request };
9
23
  }
10
24
 
25
+ // src/socials/threads/utils.ts
26
+ var client = createGraphClient("https://graph.threads.net/v1.0");
27
+ var getThreadsData = client.request;
28
+
11
29
  // src/socials/threads/getters.ts
12
30
  var getThread = (token, id, fields) => {
13
31
  return getThreadsData(token, id, {
14
- fields: fields ? fields.join(",") : "text,permalink,username"
32
+ params: { fields: fields ? fields.join(",") : "text,permalink,username" }
15
33
  });
16
34
  };
17
35
  var getThreads = (token, fields) => {
18
36
  return getThreadsData(token, "me/threads", {
19
- fields: fields ? fields.join(",") : "text,permalink,username"
37
+ params: { fields: fields ? fields.join(",") : "text,permalink,username" }
20
38
  }).then((res) => res?.data);
21
39
  };
22
40
  var getThreadsReplies = (token, id, fields) => {
23
41
  return getThreadsData(token, `${id}/replies`, {
24
- fields: fields ? fields.join(",") : "text,username,permalink"
42
+ params: { fields: fields ? fields.join(",") : "text,username,permalink" }
25
43
  }).then((res) => res?.data);
26
44
  };
45
+ var getThreadsContainerStatus = async (token, id) => {
46
+ const result = await getThreadsData(token, id, { params: { fields: "status,error_message" } });
47
+ if (!result?.status) return null;
48
+ return { status: result.status, error: result.error_message };
49
+ };
27
50
  export {
28
51
  getThread,
29
52
  getThreads,
53
+ getThreadsContainerStatus,
30
54
  getThreadsReplies
31
55
  };
@@ -20,68 +20,174 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/socials/threads/setters.ts
21
21
  var setters_exports = {};
22
22
  __export(setters_exports, {
23
- createContainer: () => createContainer,
24
23
  createThread: () => createThread,
25
- createThreadsPost: () => createThreadsPost,
26
- publishContainer: () => publishContainer
24
+ createThreadsPost: () => createThreadsPost
27
25
  });
28
26
  module.exports = __toCommonJS(setters_exports);
29
27
 
30
- // src/socials/threads/utils.ts
31
- function getThreadsURL(token, path, params) {
32
- return `https://graph.threads.net/v1.0/${path}?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}&access_token=${token}`;
28
+ // src/socials/meta/container.ts
29
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
30
+ async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
31
+ const deadline = Date.now() + timeoutMS;
32
+ let state = await getState();
33
+ if (!state) {
34
+ await sleep(2e3);
35
+ return null;
36
+ }
37
+ while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
38
+ await sleep(intervalMS);
39
+ state = await getState();
40
+ }
41
+ return state;
33
42
  }
34
- function getThreadsData(token, path, params, method) {
35
- return fetch(getThreadsURL(token, path, params), {
36
- method: method || "GET"
37
- }).then((res) => res.json());
43
+ function createPublisher(platform) {
44
+ const isReady = async (token, id, label, wait) => {
45
+ const state = await waitForContainer(
46
+ () => platform.getStatus(token, id),
47
+ wait
48
+ );
49
+ if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
50
+ console.error(
51
+ `[naystack] ${platform.name} ${label} ${id} is ${state.status}${state.error ? `: ${state.error}` : ""}`
52
+ );
53
+ return false;
54
+ }
55
+ return true;
56
+ };
57
+ return {
58
+ /** Creates a container, waits for it to finish processing, then publishes it. */
59
+ publish: async (token, params, wait) => {
60
+ const containerID = await platform.createContainer(token, params);
61
+ if (!containerID) return null;
62
+ if (!await isReady(token, containerID, "container", wait)) return null;
63
+ return platform.publish(token, containerID);
64
+ },
65
+ /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
66
+ createChildren: async (token, items, wait) => {
67
+ const children = [];
68
+ for (const item of items) {
69
+ const childID = await platform.createContainer(token, {
70
+ ...item,
71
+ is_carousel_item: true
72
+ });
73
+ if (!childID) return null;
74
+ if (!await isReady(token, childID, "carousel item", wait))
75
+ return null;
76
+ children.push(childID);
77
+ }
78
+ return children;
79
+ }
80
+ };
38
81
  }
39
82
 
40
- // src/socials/threads/setters.ts
41
- function createContainer(token, text, reply_to_id) {
42
- return getThreadsData(
43
- token,
44
- "me/threads",
45
- {
46
- media_type: "TEXT",
47
- text,
48
- ...reply_to_id ? { reply_to_id } : {}
49
- },
50
- "POST"
51
- ).then((res) => res?.id);
83
+ // src/socials/meta/request.ts
84
+ function createGraphClient(baseURL) {
85
+ const buildURL = (token, path, params = {}) => {
86
+ const search = new URLSearchParams();
87
+ for (const [key, value] of Object.entries(params)) {
88
+ if (value !== void 0) search.set(key, String(value));
89
+ }
90
+ search.set("access_token", token);
91
+ return `${baseURL}/${path}?${search.toString()}`;
92
+ };
93
+ const request = async (token, path, options = {}) => {
94
+ const response = await fetch(buildURL(token, path, options.params), {
95
+ method: options.method || (options.body ? "POST" : "GET"),
96
+ headers: {
97
+ Authorization: `Bearer ${token}`,
98
+ ...options.body ? { "Content-Type": "application/json" } : {}
99
+ },
100
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
101
+ });
102
+ return response.json().catch(() => null);
103
+ };
104
+ return { buildURL, request };
105
+ }
106
+ function readGraphID(label, result) {
107
+ if (result?.error) {
108
+ console.error(`[naystack] ${label} failed: ${result.error.message}`);
109
+ return null;
110
+ }
111
+ return result?.id || null;
52
112
  }
53
- function publishContainer(token, creation_id) {
54
- return getThreadsData(
113
+
114
+ // src/socials/threads/utils.ts
115
+ var client = createGraphClient("https://graph.threads.net/v1.0");
116
+ var getThreadsData = client.request;
117
+
118
+ // src/socials/threads/getters.ts
119
+ var getThreadsContainerStatus = async (token, id) => {
120
+ const result = await getThreadsData(token, id, { params: { fields: "status,error_message" } });
121
+ if (!result?.status) return null;
122
+ return { status: result.status, error: result.error_message };
123
+ };
124
+
125
+ // src/socials/threads/setters.ts
126
+ var publisher = createPublisher({
127
+ name: "Threads",
128
+ getStatus: getThreadsContainerStatus,
129
+ createContainer: async (token, params) => readGraphID(
130
+ "Threads container",
131
+ await getThreadsData(token, "me/threads", {
132
+ params,
133
+ method: "POST"
134
+ })
135
+ ),
136
+ publish: async (token, creationID) => readGraphID(
137
+ "Threads publish",
138
+ await getThreadsData(token, "me/threads_publish", {
139
+ params: { creation_id: creationID },
140
+ method: "POST"
141
+ })
142
+ )
143
+ });
144
+ var mediaParams = (media) => ({
145
+ media_type: media ? media.type === "video" /* Video */ ? "VIDEO" : "IMAGE" : "TEXT",
146
+ image_url: media?.type === "photo" /* Photo */ ? media.url : void 0,
147
+ video_url: media?.type === "video" /* Video */ ? media.url : void 0,
148
+ alt_text: media?.altText
149
+ });
150
+ var createThreadsPost = async (token, input, replyToID) => {
151
+ const post = typeof input === "string" ? { text: input } : input;
152
+ const media = post.media ? [post.media].flat() : [];
153
+ const shared = {
154
+ text: post.text,
155
+ reply_to_id: post.replyToID || replyToID,
156
+ reply_control: post.replyControl
157
+ };
158
+ if (media.length > 1) {
159
+ const children = await publisher.createChildren(
160
+ token,
161
+ media.map((item) => mediaParams(item)),
162
+ post.wait
163
+ );
164
+ if (!children) return null;
165
+ return publisher.publish(
166
+ token,
167
+ { ...shared, media_type: "CAROUSEL", children: children.join(",") },
168
+ post.wait
169
+ );
170
+ }
171
+ return publisher.publish(
55
172
  token,
56
- "me/threads_publish",
57
173
  {
58
- creation_id
174
+ ...mediaParams(media[0]),
175
+ ...shared,
176
+ link_attachment: post.linkAttachment
59
177
  },
60
- "POST"
61
- ).then((res) => res?.id);
62
- }
63
- var createThreadsPost = async (token, text, reply_to_id) => {
64
- const containerID = await createContainer(token, text, reply_to_id);
65
- if (!containerID) return null;
66
- await new Promise((resolve) => setTimeout(resolve, 2e3));
67
- return publishContainer(token, containerID);
178
+ post.wait
179
+ );
68
180
  };
69
- var createThread = async (token, threads) => {
181
+ var createThread = async (token, posts) => {
70
182
  const publishedIDs = [];
71
- for (const thread of threads) {
72
- const postID = await createThreadsPost(
73
- token,
74
- thread.split("\n").join("%0A"),
75
- publishedIDs.at(-1)
76
- );
183
+ for (const post of posts) {
184
+ const postID = await createThreadsPost(token, post, publishedIDs.at(-1));
77
185
  if (postID) publishedIDs.push(postID);
78
186
  }
79
187
  return publishedIDs[0];
80
188
  };
81
189
  // Annotate the CommonJS export names for ESM import in node:
82
190
  0 && (module.exports = {
83
- createContainer,
84
191
  createThread,
85
- createThreadsPost,
86
- publishContainer
192
+ createThreadsPost
87
193
  });
@@ -1,50 +1,41 @@
1
+ import { ThreadsPostInput } from './types.mjs';
2
+ import '../meta/container.mjs';
3
+ import '../meta/types.mjs';
4
+
1
5
  /**
2
- * Creates a Threads container (draft post). Must be published with `publishContainer`.
3
- * This is a low-level function prefer using {@link createThreadsPost} which handles both steps.
6
+ * Publishes to Threads text, image, video or carousel, chosen from the media you
7
+ * pass. Creates the container, waits for Threads to finish processing it, then publishes.
4
8
  *
5
- * @param token - Threads access token.
6
- * @param text - Post text.
7
- * @param reply_to_id - Optional parent post id (for replies).
8
- * @returns Promise of the container creation id.
9
- * @category Socials
10
- */
11
- declare function createContainer(token: string, text: string, reply_to_id?: string): Promise<string | undefined>;
12
- /**
13
- * Publishes a Threads container (created with `createContainer`).
14
- * This is a low-level function — prefer using {@link createThreadsPost} which handles both steps.
9
+ * Media URLs must be publicly reachable — Threads downloads them server-side.
15
10
  *
16
11
  * @param token - Threads access token.
17
- * @param creation_id - Container id from `createContainer`.
18
- * @returns Promise of the published post id.
19
- * @category Socials
20
- */
21
- declare function publishContainer(token: string, creation_id: string): Promise<string | undefined>;
22
- /**
23
- * Creates and publishes a single Threads post. Handles the two-step container + publish flow
24
- * with an automatic 2-second delay between creation and publishing (required by the Threads API).
25
- *
26
- * @param token - Threads access token.
27
- * @param text - Post text.
28
- * @param reply_to_id - Optional parent post id (to make this post a reply).
29
- * @returns Promise of the published post id, or `null` if creation failed.
12
+ * @param input - Post text, or a {@link ThreadsPostInput} for media, replies and reply controls.
13
+ * @param replyToID - Parent post id, to make this post a reply. Also settable on `input`.
14
+ * @returns Promise of the published post id, or `null` if any step failed (the API's error is logged).
30
15
  *
31
16
  * @example
32
17
  * ```ts
33
18
  * import { createThreadsPost } from "naystack/socials";
34
19
  *
35
- * const postId = await createThreadsPost(accessToken, "Hello from Naystack!");
36
- * console.log("Published:", postId);
20
+ * await createThreadsPost(accessToken, "Hello from Naystack!");
21
+ *
22
+ * await createThreadsPost(accessToken, {
23
+ * text: "Campaign recap",
24
+ * media: [
25
+ * { url: "https://cdn.example.com/1.jpg", type: "image" },
26
+ * { url: "https://cdn.example.com/2.mp4", type: "video" },
27
+ * ],
28
+ * });
37
29
  * ```
38
30
  *
39
31
  * @category Socials
40
32
  */
41
- declare const createThreadsPost: (token: string, text: string, reply_to_id?: string) => Promise<string | null | undefined>;
33
+ declare const createThreadsPost: (token: string, input: string | ThreadsPostInput, replyToID?: string) => Promise<string | null>;
42
34
  /**
43
- * Creates a thread — a sequence of posts where each replies to the previous one.
44
- * Newlines in post text are converted to `%0A` for the API.
35
+ * Publishes a thread — a sequence of posts where each replies to the previous one.
45
36
  *
46
37
  * @param token - Threads access token.
47
- * @param threads - Array of post text strings (in order). Each post after the first becomes a reply to the previous.
38
+ * @param posts - Posts in order, as text or {@link ThreadsPostInput}.
48
39
  * @returns Promise of the first published post's id.
49
40
  *
50
41
  * @example
@@ -54,12 +45,12 @@ declare const createThreadsPost: (token: string, text: string, reply_to_id?: str
54
45
  * const firstPostId = await createThread(accessToken, [
55
46
  * "First post in thread",
56
47
  * "Second post (reply to first)",
57
- * "Third post (reply to second)",
48
+ * { text: "Third, with a picture", media: { url: "https://cdn.example.com/3.jpg", type: "image" } },
58
49
  * ]);
59
50
  * ```
60
51
  *
61
52
  * @category Socials
62
53
  */
63
- declare const createThread: (token: string, threads: string[]) => Promise<string | undefined>;
54
+ declare const createThread: (token: string, posts: (string | ThreadsPostInput)[]) => Promise<string | undefined>;
64
55
 
65
- export { createContainer, createThread, createThreadsPost, publishContainer };
56
+ export { createThread, createThreadsPost };
@@ -1,50 +1,41 @@
1
+ import { ThreadsPostInput } from './types.js';
2
+ import '../meta/container.js';
3
+ import '../meta/types.js';
4
+
1
5
  /**
2
- * Creates a Threads container (draft post). Must be published with `publishContainer`.
3
- * This is a low-level function prefer using {@link createThreadsPost} which handles both steps.
6
+ * Publishes to Threads text, image, video or carousel, chosen from the media you
7
+ * pass. Creates the container, waits for Threads to finish processing it, then publishes.
4
8
  *
5
- * @param token - Threads access token.
6
- * @param text - Post text.
7
- * @param reply_to_id - Optional parent post id (for replies).
8
- * @returns Promise of the container creation id.
9
- * @category Socials
10
- */
11
- declare function createContainer(token: string, text: string, reply_to_id?: string): Promise<string | undefined>;
12
- /**
13
- * Publishes a Threads container (created with `createContainer`).
14
- * This is a low-level function — prefer using {@link createThreadsPost} which handles both steps.
9
+ * Media URLs must be publicly reachable — Threads downloads them server-side.
15
10
  *
16
11
  * @param token - Threads access token.
17
- * @param creation_id - Container id from `createContainer`.
18
- * @returns Promise of the published post id.
19
- * @category Socials
20
- */
21
- declare function publishContainer(token: string, creation_id: string): Promise<string | undefined>;
22
- /**
23
- * Creates and publishes a single Threads post. Handles the two-step container + publish flow
24
- * with an automatic 2-second delay between creation and publishing (required by the Threads API).
25
- *
26
- * @param token - Threads access token.
27
- * @param text - Post text.
28
- * @param reply_to_id - Optional parent post id (to make this post a reply).
29
- * @returns Promise of the published post id, or `null` if creation failed.
12
+ * @param input - Post text, or a {@link ThreadsPostInput} for media, replies and reply controls.
13
+ * @param replyToID - Parent post id, to make this post a reply. Also settable on `input`.
14
+ * @returns Promise of the published post id, or `null` if any step failed (the API's error is logged).
30
15
  *
31
16
  * @example
32
17
  * ```ts
33
18
  * import { createThreadsPost } from "naystack/socials";
34
19
  *
35
- * const postId = await createThreadsPost(accessToken, "Hello from Naystack!");
36
- * console.log("Published:", postId);
20
+ * await createThreadsPost(accessToken, "Hello from Naystack!");
21
+ *
22
+ * await createThreadsPost(accessToken, {
23
+ * text: "Campaign recap",
24
+ * media: [
25
+ * { url: "https://cdn.example.com/1.jpg", type: "image" },
26
+ * { url: "https://cdn.example.com/2.mp4", type: "video" },
27
+ * ],
28
+ * });
37
29
  * ```
38
30
  *
39
31
  * @category Socials
40
32
  */
41
- declare const createThreadsPost: (token: string, text: string, reply_to_id?: string) => Promise<string | null | undefined>;
33
+ declare const createThreadsPost: (token: string, input: string | ThreadsPostInput, replyToID?: string) => Promise<string | null>;
42
34
  /**
43
- * Creates a thread — a sequence of posts where each replies to the previous one.
44
- * Newlines in post text are converted to `%0A` for the API.
35
+ * Publishes a thread — a sequence of posts where each replies to the previous one.
45
36
  *
46
37
  * @param token - Threads access token.
47
- * @param threads - Array of post text strings (in order). Each post after the first becomes a reply to the previous.
38
+ * @param posts - Posts in order, as text or {@link ThreadsPostInput}.
48
39
  * @returns Promise of the first published post's id.
49
40
  *
50
41
  * @example
@@ -54,12 +45,12 @@ declare const createThreadsPost: (token: string, text: string, reply_to_id?: str
54
45
  * const firstPostId = await createThread(accessToken, [
55
46
  * "First post in thread",
56
47
  * "Second post (reply to first)",
57
- * "Third post (reply to second)",
48
+ * { text: "Third, with a picture", media: { url: "https://cdn.example.com/3.jpg", type: "image" } },
58
49
  * ]);
59
50
  * ```
60
51
  *
61
52
  * @category Socials
62
53
  */
63
- declare const createThread: (token: string, threads: string[]) => Promise<string | undefined>;
54
+ declare const createThread: (token: string, posts: (string | ThreadsPostInput)[]) => Promise<string | undefined>;
64
55
 
65
- export { createContainer, createThread, createThreadsPost, publishContainer };
56
+ export { createThread, createThreadsPost };
@@ -1,57 +1,165 @@
1
- // src/socials/threads/utils.ts
2
- function getThreadsURL(token, path, params) {
3
- return `https://graph.threads.net/v1.0/${path}?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}&access_token=${token}`;
1
+ // src/socials/meta/container.ts
2
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3
+ async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
4
+ const deadline = Date.now() + timeoutMS;
5
+ let state = await getState();
6
+ if (!state) {
7
+ await sleep(2e3);
8
+ return null;
9
+ }
10
+ while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
11
+ await sleep(intervalMS);
12
+ state = await getState();
13
+ }
14
+ return state;
4
15
  }
5
- function getThreadsData(token, path, params, method) {
6
- return fetch(getThreadsURL(token, path, params), {
7
- method: method || "GET"
8
- }).then((res) => res.json());
16
+ function createPublisher(platform) {
17
+ const isReady = async (token, id, label, wait) => {
18
+ const state = await waitForContainer(
19
+ () => platform.getStatus(token, id),
20
+ wait
21
+ );
22
+ if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
23
+ console.error(
24
+ `[naystack] ${platform.name} ${label} ${id} is ${state.status}${state.error ? `: ${state.error}` : ""}`
25
+ );
26
+ return false;
27
+ }
28
+ return true;
29
+ };
30
+ return {
31
+ /** Creates a container, waits for it to finish processing, then publishes it. */
32
+ publish: async (token, params, wait) => {
33
+ const containerID = await platform.createContainer(token, params);
34
+ if (!containerID) return null;
35
+ if (!await isReady(token, containerID, "container", wait)) return null;
36
+ return platform.publish(token, containerID);
37
+ },
38
+ /** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
39
+ createChildren: async (token, items, wait) => {
40
+ const children = [];
41
+ for (const item of items) {
42
+ const childID = await platform.createContainer(token, {
43
+ ...item,
44
+ is_carousel_item: true
45
+ });
46
+ if (!childID) return null;
47
+ if (!await isReady(token, childID, "carousel item", wait))
48
+ return null;
49
+ children.push(childID);
50
+ }
51
+ return children;
52
+ }
53
+ };
9
54
  }
10
55
 
11
- // src/socials/threads/setters.ts
12
- function createContainer(token, text, reply_to_id) {
13
- return getThreadsData(
14
- token,
15
- "me/threads",
16
- {
17
- media_type: "TEXT",
18
- text,
19
- ...reply_to_id ? { reply_to_id } : {}
20
- },
21
- "POST"
22
- ).then((res) => res?.id);
56
+ // src/socials/meta/request.ts
57
+ function createGraphClient(baseURL) {
58
+ const buildURL = (token, path, params = {}) => {
59
+ const search = new URLSearchParams();
60
+ for (const [key, value] of Object.entries(params)) {
61
+ if (value !== void 0) search.set(key, String(value));
62
+ }
63
+ search.set("access_token", token);
64
+ return `${baseURL}/${path}?${search.toString()}`;
65
+ };
66
+ const request = async (token, path, options = {}) => {
67
+ const response = await fetch(buildURL(token, path, options.params), {
68
+ method: options.method || (options.body ? "POST" : "GET"),
69
+ headers: {
70
+ Authorization: `Bearer ${token}`,
71
+ ...options.body ? { "Content-Type": "application/json" } : {}
72
+ },
73
+ ...options.body ? { body: JSON.stringify(options.body) } : {}
74
+ });
75
+ return response.json().catch(() => null);
76
+ };
77
+ return { buildURL, request };
78
+ }
79
+ function readGraphID(label, result) {
80
+ if (result?.error) {
81
+ console.error(`[naystack] ${label} failed: ${result.error.message}`);
82
+ return null;
83
+ }
84
+ return result?.id || null;
23
85
  }
24
- function publishContainer(token, creation_id) {
25
- return getThreadsData(
86
+
87
+ // src/socials/threads/utils.ts
88
+ var client = createGraphClient("https://graph.threads.net/v1.0");
89
+ var getThreadsData = client.request;
90
+
91
+ // src/socials/threads/getters.ts
92
+ var getThreadsContainerStatus = async (token, id) => {
93
+ const result = await getThreadsData(token, id, { params: { fields: "status,error_message" } });
94
+ if (!result?.status) return null;
95
+ return { status: result.status, error: result.error_message };
96
+ };
97
+
98
+ // src/socials/threads/setters.ts
99
+ var publisher = createPublisher({
100
+ name: "Threads",
101
+ getStatus: getThreadsContainerStatus,
102
+ createContainer: async (token, params) => readGraphID(
103
+ "Threads container",
104
+ await getThreadsData(token, "me/threads", {
105
+ params,
106
+ method: "POST"
107
+ })
108
+ ),
109
+ publish: async (token, creationID) => readGraphID(
110
+ "Threads publish",
111
+ await getThreadsData(token, "me/threads_publish", {
112
+ params: { creation_id: creationID },
113
+ method: "POST"
114
+ })
115
+ )
116
+ });
117
+ var mediaParams = (media) => ({
118
+ media_type: media ? media.type === "video" /* Video */ ? "VIDEO" : "IMAGE" : "TEXT",
119
+ image_url: media?.type === "photo" /* Photo */ ? media.url : void 0,
120
+ video_url: media?.type === "video" /* Video */ ? media.url : void 0,
121
+ alt_text: media?.altText
122
+ });
123
+ var createThreadsPost = async (token, input, replyToID) => {
124
+ const post = typeof input === "string" ? { text: input } : input;
125
+ const media = post.media ? [post.media].flat() : [];
126
+ const shared = {
127
+ text: post.text,
128
+ reply_to_id: post.replyToID || replyToID,
129
+ reply_control: post.replyControl
130
+ };
131
+ if (media.length > 1) {
132
+ const children = await publisher.createChildren(
133
+ token,
134
+ media.map((item) => mediaParams(item)),
135
+ post.wait
136
+ );
137
+ if (!children) return null;
138
+ return publisher.publish(
139
+ token,
140
+ { ...shared, media_type: "CAROUSEL", children: children.join(",") },
141
+ post.wait
142
+ );
143
+ }
144
+ return publisher.publish(
26
145
  token,
27
- "me/threads_publish",
28
146
  {
29
- creation_id
147
+ ...mediaParams(media[0]),
148
+ ...shared,
149
+ link_attachment: post.linkAttachment
30
150
  },
31
- "POST"
32
- ).then((res) => res?.id);
33
- }
34
- var createThreadsPost = async (token, text, reply_to_id) => {
35
- const containerID = await createContainer(token, text, reply_to_id);
36
- if (!containerID) return null;
37
- await new Promise((resolve) => setTimeout(resolve, 2e3));
38
- return publishContainer(token, containerID);
151
+ post.wait
152
+ );
39
153
  };
40
- var createThread = async (token, threads) => {
154
+ var createThread = async (token, posts) => {
41
155
  const publishedIDs = [];
42
- for (const thread of threads) {
43
- const postID = await createThreadsPost(
44
- token,
45
- thread.split("\n").join("%0A"),
46
- publishedIDs.at(-1)
47
- );
156
+ for (const post of posts) {
157
+ const postID = await createThreadsPost(token, post, publishedIDs.at(-1));
48
158
  if (postID) publishedIDs.push(postID);
49
159
  }
50
160
  return publishedIDs[0];
51
161
  };
52
162
  export {
53
- createContainer,
54
163
  createThread,
55
- createThreadsPost,
56
- publishContainer
164
+ createThreadsPost
57
165
  };