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.
- package/README.md +189 -31
- package/dist/auth/index.cjs.js +158 -18
- package/dist/auth/index.d.mts +3 -1
- package/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.esm.js +158 -18
- package/dist/auth/instagram/index.cjs.js +158 -18
- package/dist/auth/instagram/index.d.mts +6 -1
- package/dist/auth/instagram/index.d.ts +6 -1
- package/dist/auth/instagram/index.esm.js +158 -18
- package/dist/auth/instagram/route.cjs.js +158 -18
- package/dist/auth/instagram/route.d.mts +3 -1
- package/dist/auth/instagram/route.d.ts +3 -1
- package/dist/auth/instagram/route.esm.js +158 -18
- package/dist/auth/instagram/utils.cjs.js +2 -2
- package/dist/auth/instagram/utils.d.mts +13 -2
- package/dist/auth/instagram/utils.d.ts +13 -2
- package/dist/auth/instagram/utils.esm.js +2 -2
- package/dist/file/setup.d.mts +13 -5
- package/dist/file/setup.d.ts +13 -5
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/socials/index.cjs.js +298 -72
- package/dist/socials/index.d.mts +6 -4
- package/dist/socials/index.d.ts +6 -4
- package/dist/socials/index.esm.js +296 -72
- package/dist/socials/instagram/getters.cjs.js +76 -25
- package/dist/socials/instagram/getters.d.mts +56 -6
- package/dist/socials/instagram/getters.d.ts +56 -6
- package/dist/socials/instagram/getters.esm.js +74 -25
- package/dist/socials/instagram/setters.cjs.js +192 -14
- package/dist/socials/instagram/setters.d.mts +43 -3
- package/dist/socials/instagram/setters.d.ts +43 -3
- package/dist/socials/instagram/setters.esm.js +191 -14
- package/dist/socials/instagram/types.d.mts +48 -11
- package/dist/socials/instagram/types.d.ts +48 -11
- package/dist/socials/instagram/utils.cjs.js +28 -15
- package/dist/socials/instagram/utils.d.mts +7 -18
- package/dist/socials/instagram/utils.d.ts +7 -18
- package/dist/socials/instagram/utils.esm.js +26 -14
- package/dist/socials/instagram/webhook.cjs.js +1 -1
- package/dist/socials/instagram/webhook.esm.js +1 -1
- package/dist/socials/meta/container.cjs.js +82 -0
- package/dist/socials/meta/container.d.mts +38 -0
- package/dist/socials/meta/container.d.ts +38 -0
- package/dist/socials/meta/container.esm.js +57 -0
- package/dist/socials/meta/request.cjs.js +60 -0
- package/dist/socials/meta/request.d.mts +50 -0
- package/dist/socials/meta/request.d.ts +50 -0
- package/dist/socials/meta/request.esm.js +34 -0
- package/dist/socials/meta/types.cjs.js +34 -0
- package/dist/socials/meta/types.d.mts +51 -0
- package/dist/socials/meta/types.d.ts +51 -0
- package/dist/socials/meta/types.esm.js +9 -0
- package/dist/socials/{meta-webhook.cjs.js → meta/webhook.cjs.js} +4 -4
- package/dist/socials/{meta-webhook.esm.js → meta/webhook.esm.js} +1 -1
- package/dist/socials/threads/getters.cjs.js +36 -11
- package/dist/socials/threads/getters.d.mts +23 -2
- package/dist/socials/threads/getters.d.ts +23 -2
- package/dist/socials/threads/getters.esm.js +35 -11
- package/dist/socials/threads/setters.cjs.js +150 -44
- package/dist/socials/threads/setters.d.mts +25 -34
- package/dist/socials/threads/setters.d.ts +25 -34
- package/dist/socials/threads/setters.esm.js +149 -41
- package/dist/socials/threads/types.d.mts +40 -1
- package/dist/socials/threads/types.d.ts +40 -1
- package/dist/socials/threads/utils.cjs.js +29 -11
- package/dist/socials/threads/utils.d.mts +8 -17
- package/dist/socials/threads/utils.d.ts +8 -17
- package/dist/socials/threads/utils.esm.js +27 -10
- package/dist/socials/threads/webhook.cjs.js +1 -1
- package/dist/socials/threads/webhook.esm.js +1 -1
- package/package.json +1 -1
- /package/dist/socials/{meta-webhook.d.mts → meta/webhook.d.mts} +0 -0
- /package/dist/socials/{meta-webhook.d.ts → meta/webhook.d.ts} +0 -0
|
@@ -1,31 +1,55 @@
|
|
|
1
|
-
// src/socials/
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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/
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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/
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
174
|
+
...mediaParams(media[0]),
|
|
175
|
+
...shared,
|
|
176
|
+
link_attachment: post.linkAttachment
|
|
59
177
|
},
|
|
60
|
-
|
|
61
|
-
)
|
|
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,
|
|
181
|
+
var createThread = async (token, posts) => {
|
|
70
182
|
const publishedIDs = [];
|
|
71
|
-
for (const
|
|
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
|
-
*
|
|
3
|
-
*
|
|
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
|
-
*
|
|
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
|
|
18
|
-
* @
|
|
19
|
-
* @
|
|
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
|
-
*
|
|
36
|
-
*
|
|
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,
|
|
33
|
+
declare const createThreadsPost: (token: string, input: string | ThreadsPostInput, replyToID?: string) => Promise<string | null>;
|
|
42
34
|
/**
|
|
43
|
-
*
|
|
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
|
|
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
|
|
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,
|
|
54
|
+
declare const createThread: (token: string, posts: (string | ThreadsPostInput)[]) => Promise<string | undefined>;
|
|
64
55
|
|
|
65
|
-
export {
|
|
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
|
-
*
|
|
3
|
-
*
|
|
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
|
-
*
|
|
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
|
|
18
|
-
* @
|
|
19
|
-
* @
|
|
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
|
-
*
|
|
36
|
-
*
|
|
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,
|
|
33
|
+
declare const createThreadsPost: (token: string, input: string | ThreadsPostInput, replyToID?: string) => Promise<string | null>;
|
|
42
34
|
/**
|
|
43
|
-
*
|
|
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
|
|
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
|
|
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,
|
|
54
|
+
declare const createThread: (token: string, posts: (string | ThreadsPostInput)[]) => Promise<string | undefined>;
|
|
64
55
|
|
|
65
|
-
export {
|
|
56
|
+
export { createThread, createThreadsPost };
|
|
@@ -1,57 +1,165 @@
|
|
|
1
|
-
// src/socials/
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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/
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
147
|
+
...mediaParams(media[0]),
|
|
148
|
+
...shared,
|
|
149
|
+
link_attachment: post.linkAttachment
|
|
30
150
|
},
|
|
31
|
-
|
|
32
|
-
)
|
|
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,
|
|
154
|
+
var createThread = async (token, posts) => {
|
|
41
155
|
const publishedIDs = [];
|
|
42
|
-
for (const
|
|
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
|
};
|