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,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GraphError, ContainerState } from '../meta/types.mjs';
|
|
2
|
+
import { InstagramConversation, InstagramMedia, InstagramMessage, InstagramUser } from './types.mjs';
|
|
3
|
+
import '../meta/container.mjs';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Fetches an Instagram user's profile. Defaults to the authenticated user (`"me"`).
|
|
@@ -18,7 +20,7 @@ import { InstagramConversation, InstagramError, InstagramMedia, InstagramMessage
|
|
|
18
20
|
*
|
|
19
21
|
* @category Socials
|
|
20
22
|
*/
|
|
21
|
-
declare const getInstagramUser: <T = InstagramUser>(token: string, id?: string, fields?: string[]) => Promise<(T &
|
|
23
|
+
declare const getInstagramUser: <T = InstagramUser>(token: string, id?: string, fields?: string[]) => Promise<(T & GraphError) | null>;
|
|
22
24
|
/**
|
|
23
25
|
* Fetches the authenticated user's recent Instagram media.
|
|
24
26
|
*
|
|
@@ -41,7 +43,7 @@ declare const getInstagramUser: <T = InstagramUser>(token: string, id?: string,
|
|
|
41
43
|
*/
|
|
42
44
|
declare const getInstagramMedia: <T = InstagramMedia>(token: string, fields?: string[], limit?: number) => Promise<({
|
|
43
45
|
data: T[];
|
|
44
|
-
} &
|
|
46
|
+
} & GraphError) | null>;
|
|
45
47
|
/**
|
|
46
48
|
* Fetches Instagram conversations (DM threads) with pagination support.
|
|
47
49
|
*
|
|
@@ -92,7 +94,7 @@ declare const getInstagramConversations: (token: string, limit?: number, cursor?
|
|
|
92
94
|
*/
|
|
93
95
|
declare const getInstagramConversationsByUser: (token: string, userID: string) => Promise<({
|
|
94
96
|
data: InstagramConversation[];
|
|
95
|
-
} &
|
|
97
|
+
} & GraphError) | null>;
|
|
96
98
|
/**
|
|
97
99
|
* Fetches the single conversation between the authenticated user and the given user.
|
|
98
100
|
* Filters for conversations with exactly 2 participants.
|
|
@@ -149,6 +151,54 @@ declare const getInstagramConversation: (token: string, id: string, cursor?: str
|
|
|
149
151
|
*
|
|
150
152
|
* @category Socials
|
|
151
153
|
*/
|
|
152
|
-
declare const getInstagramMessage: <T = InstagramMessage>(token: string, id: string, fields?: string[]) => Promise<(T &
|
|
154
|
+
declare const getInstagramMessage: <T = InstagramMessage>(token: string, id: string, fields?: string[]) => Promise<(T & GraphError) | null>;
|
|
155
|
+
/**
|
|
156
|
+
* Reads a media container's processing status. Videos and reels must reach
|
|
157
|
+
* `FINISHED` before they can be published.
|
|
158
|
+
*
|
|
159
|
+
* @param token - Instagram access token.
|
|
160
|
+
* @param id - Container id from `createInstagramContainer`.
|
|
161
|
+
* @returns Promise of `{ status, error? }`, or `null` if the status was unreadable.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* import { getInstagramContainerStatus } from "naystack/socials";
|
|
166
|
+
*
|
|
167
|
+
* const state = await getInstagramContainerStatus(accessToken, containerId);
|
|
168
|
+
* if (state?.status === "FINISHED") await publishInstagramContainer(accessToken, containerId);
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* @category Socials
|
|
172
|
+
*/
|
|
173
|
+
declare const getInstagramContainerStatus: (token: string, id: string) => Promise<ContainerState | null>;
|
|
174
|
+
/**
|
|
175
|
+
* Checks whether an access token is allowed to publish.
|
|
176
|
+
*
|
|
177
|
+
* Meta exposes no scope-listing endpoint for Instagram Login tokens, so this probes
|
|
178
|
+
* the publishing quota — a read-only endpoint that needs exactly the same
|
|
179
|
+
* `instagram_business_content_publish` scope publishing does. Nothing is posted.
|
|
180
|
+
*
|
|
181
|
+
* Worth calling before {@link createInstagramPost} in a scheduled job: a token whose
|
|
182
|
+
* access was revoked fails here with a clear reason, instead of surfacing later as a
|
|
183
|
+
* container error.
|
|
184
|
+
*
|
|
185
|
+
* Rejects — rather than answering `false` — if the request itself fails, so a network
|
|
186
|
+
* blip is never reported as a missing permission.
|
|
187
|
+
*
|
|
188
|
+
* @param token - Instagram access token.
|
|
189
|
+
* @returns Promise of `true` if the token can publish. On `false`, the API's reason is logged.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* import { canPublishToInstagram } from "naystack/socials";
|
|
194
|
+
*
|
|
195
|
+
* if (!(await canPublishToInstagram(process.env.INSTAGRAM_ACCESS_TOKEN!))) {
|
|
196
|
+
* throw new Error("Instagram token cannot publish — reconnect with the publish scope");
|
|
197
|
+
* }
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @category Socials
|
|
201
|
+
*/
|
|
202
|
+
declare const canPublishToInstagram: (token: string) => Promise<boolean>;
|
|
153
203
|
|
|
154
|
-
export { getInstagramConversation, getInstagramConversationByUser, getInstagramConversations, getInstagramConversationsByUser, getInstagramMedia, getInstagramMessage, getInstagramUser };
|
|
204
|
+
export { canPublishToInstagram, getInstagramContainerStatus, getInstagramConversation, getInstagramConversationByUser, getInstagramConversations, getInstagramConversationsByUser, getInstagramMedia, getInstagramMessage, getInstagramUser };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GraphError, ContainerState } from '../meta/types.js';
|
|
2
|
+
import { InstagramConversation, InstagramMedia, InstagramMessage, InstagramUser } from './types.js';
|
|
3
|
+
import '../meta/container.js';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Fetches an Instagram user's profile. Defaults to the authenticated user (`"me"`).
|
|
@@ -18,7 +20,7 @@ import { InstagramConversation, InstagramError, InstagramMedia, InstagramMessage
|
|
|
18
20
|
*
|
|
19
21
|
* @category Socials
|
|
20
22
|
*/
|
|
21
|
-
declare const getInstagramUser: <T = InstagramUser>(token: string, id?: string, fields?: string[]) => Promise<(T &
|
|
23
|
+
declare const getInstagramUser: <T = InstagramUser>(token: string, id?: string, fields?: string[]) => Promise<(T & GraphError) | null>;
|
|
22
24
|
/**
|
|
23
25
|
* Fetches the authenticated user's recent Instagram media.
|
|
24
26
|
*
|
|
@@ -41,7 +43,7 @@ declare const getInstagramUser: <T = InstagramUser>(token: string, id?: string,
|
|
|
41
43
|
*/
|
|
42
44
|
declare const getInstagramMedia: <T = InstagramMedia>(token: string, fields?: string[], limit?: number) => Promise<({
|
|
43
45
|
data: T[];
|
|
44
|
-
} &
|
|
46
|
+
} & GraphError) | null>;
|
|
45
47
|
/**
|
|
46
48
|
* Fetches Instagram conversations (DM threads) with pagination support.
|
|
47
49
|
*
|
|
@@ -92,7 +94,7 @@ declare const getInstagramConversations: (token: string, limit?: number, cursor?
|
|
|
92
94
|
*/
|
|
93
95
|
declare const getInstagramConversationsByUser: (token: string, userID: string) => Promise<({
|
|
94
96
|
data: InstagramConversation[];
|
|
95
|
-
} &
|
|
97
|
+
} & GraphError) | null>;
|
|
96
98
|
/**
|
|
97
99
|
* Fetches the single conversation between the authenticated user and the given user.
|
|
98
100
|
* Filters for conversations with exactly 2 participants.
|
|
@@ -149,6 +151,54 @@ declare const getInstagramConversation: (token: string, id: string, cursor?: str
|
|
|
149
151
|
*
|
|
150
152
|
* @category Socials
|
|
151
153
|
*/
|
|
152
|
-
declare const getInstagramMessage: <T = InstagramMessage>(token: string, id: string, fields?: string[]) => Promise<(T &
|
|
154
|
+
declare const getInstagramMessage: <T = InstagramMessage>(token: string, id: string, fields?: string[]) => Promise<(T & GraphError) | null>;
|
|
155
|
+
/**
|
|
156
|
+
* Reads a media container's processing status. Videos and reels must reach
|
|
157
|
+
* `FINISHED` before they can be published.
|
|
158
|
+
*
|
|
159
|
+
* @param token - Instagram access token.
|
|
160
|
+
* @param id - Container id from `createInstagramContainer`.
|
|
161
|
+
* @returns Promise of `{ status, error? }`, or `null` if the status was unreadable.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* import { getInstagramContainerStatus } from "naystack/socials";
|
|
166
|
+
*
|
|
167
|
+
* const state = await getInstagramContainerStatus(accessToken, containerId);
|
|
168
|
+
* if (state?.status === "FINISHED") await publishInstagramContainer(accessToken, containerId);
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* @category Socials
|
|
172
|
+
*/
|
|
173
|
+
declare const getInstagramContainerStatus: (token: string, id: string) => Promise<ContainerState | null>;
|
|
174
|
+
/**
|
|
175
|
+
* Checks whether an access token is allowed to publish.
|
|
176
|
+
*
|
|
177
|
+
* Meta exposes no scope-listing endpoint for Instagram Login tokens, so this probes
|
|
178
|
+
* the publishing quota — a read-only endpoint that needs exactly the same
|
|
179
|
+
* `instagram_business_content_publish` scope publishing does. Nothing is posted.
|
|
180
|
+
*
|
|
181
|
+
* Worth calling before {@link createInstagramPost} in a scheduled job: a token whose
|
|
182
|
+
* access was revoked fails here with a clear reason, instead of surfacing later as a
|
|
183
|
+
* container error.
|
|
184
|
+
*
|
|
185
|
+
* Rejects — rather than answering `false` — if the request itself fails, so a network
|
|
186
|
+
* blip is never reported as a missing permission.
|
|
187
|
+
*
|
|
188
|
+
* @param token - Instagram access token.
|
|
189
|
+
* @returns Promise of `true` if the token can publish. On `false`, the API's reason is logged.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* import { canPublishToInstagram } from "naystack/socials";
|
|
194
|
+
*
|
|
195
|
+
* if (!(await canPublishToInstagram(process.env.INSTAGRAM_ACCESS_TOKEN!))) {
|
|
196
|
+
* throw new Error("Instagram token cannot publish — reconnect with the publish scope");
|
|
197
|
+
* }
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @category Socials
|
|
201
|
+
*/
|
|
202
|
+
declare const canPublishToInstagram: (token: string) => Promise<boolean>;
|
|
153
203
|
|
|
154
|
-
export { getInstagramConversation, getInstagramConversationByUser, getInstagramConversations, getInstagramConversationsByUser, getInstagramMedia, getInstagramMessage, getInstagramUser };
|
|
204
|
+
export { canPublishToInstagram, getInstagramContainerStatus, getInstagramConversation, getInstagramConversationByUser, getInstagramConversations, getInstagramConversationsByUser, getInstagramMedia, getInstagramMessage, getInstagramUser };
|
|
@@ -1,36 +1,55 @@
|
|
|
1
|
-
// src/socials/
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
method: postData ? "POST" : "GET",
|
|
8
|
-
body: JSON.stringify(postData),
|
|
9
|
-
headers: {
|
|
10
|
-
Authorization: `Bearer ${token}`,
|
|
11
|
-
"Content-Type": "application/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));
|
|
12
7
|
}
|
|
13
|
-
|
|
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 };
|
|
14
23
|
}
|
|
15
24
|
|
|
25
|
+
// src/socials/instagram/utils.ts
|
|
26
|
+
var client = createGraphClient("https://graph.instagram.com/v23.0");
|
|
27
|
+
var getInstagramData = client.request;
|
|
28
|
+
|
|
16
29
|
// src/socials/instagram/getters.ts
|
|
17
30
|
var getInstagramUser = (token, id, fields) => {
|
|
18
31
|
return getInstagramData(token, id || "me", {
|
|
19
|
-
|
|
32
|
+
params: {
|
|
33
|
+
fields: fields ? fields.join(",") : "username,followers_count,media_count"
|
|
34
|
+
}
|
|
20
35
|
});
|
|
21
36
|
};
|
|
22
37
|
var getInstagramMedia = (token, fields, limit = 12) => {
|
|
23
38
|
return getInstagramData(token, "me/media", {
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
params: {
|
|
40
|
+
fields: fields ? fields.join(",") : "like_count,comments_count,permalink",
|
|
41
|
+
limit
|
|
42
|
+
}
|
|
26
43
|
});
|
|
27
44
|
};
|
|
28
45
|
var getInstagramConversations = async (token, limit = 25, cursor) => {
|
|
29
46
|
const result = await getInstagramData(token, "me/conversations", {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
params: {
|
|
48
|
+
platform: "instagram",
|
|
49
|
+
fields: "participants,messages,updated_time",
|
|
50
|
+
limit,
|
|
51
|
+
after: cursor
|
|
52
|
+
}
|
|
34
53
|
});
|
|
35
54
|
return {
|
|
36
55
|
data: result?.data.map((item) => ({
|
|
@@ -46,8 +65,10 @@ var getInstagramConversationsByUser = (token, userID) => {
|
|
|
46
65
|
token,
|
|
47
66
|
"me/conversations",
|
|
48
67
|
{
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
params: {
|
|
69
|
+
fields: "participants,messages,updated_time",
|
|
70
|
+
user_id: userID
|
|
71
|
+
}
|
|
51
72
|
}
|
|
52
73
|
);
|
|
53
74
|
};
|
|
@@ -56,9 +77,11 @@ var getInstagramConversationByUser = async (token, userID) => {
|
|
|
56
77
|
return res?.data?.find((item) => item.participants?.data.length === 2);
|
|
57
78
|
};
|
|
58
79
|
var getInstagramConversation = async (token, id, cursor) => {
|
|
59
|
-
const result = await getInstagramData(token, id
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
const result = await getInstagramData(token, id, {
|
|
81
|
+
params: {
|
|
82
|
+
fields: "participants,messages,updated_time",
|
|
83
|
+
after: cursor
|
|
84
|
+
}
|
|
62
85
|
});
|
|
63
86
|
return {
|
|
64
87
|
messages: result?.messages?.data,
|
|
@@ -72,10 +95,36 @@ var getInstagramConversation = async (token, id, cursor) => {
|
|
|
72
95
|
};
|
|
73
96
|
var getInstagramMessage = (token, id, fields) => {
|
|
74
97
|
return getInstagramData(token, id, {
|
|
75
|
-
|
|
98
|
+
params: {
|
|
99
|
+
fields: fields ? fields.join(",") : "id,created_time,from,to,message"
|
|
100
|
+
}
|
|
76
101
|
});
|
|
77
102
|
};
|
|
103
|
+
var getInstagramContainerStatus = async (token, id) => {
|
|
104
|
+
const result = await getInstagramData(token, id, { params: { fields: "status_code,status" } });
|
|
105
|
+
if (!result?.status_code) return null;
|
|
106
|
+
return {
|
|
107
|
+
status: result.status_code,
|
|
108
|
+
error: result.status_code === "ERROR" ? result.status : void 0
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
var canPublishToInstagram = async (token) => {
|
|
112
|
+
const result = await getInstagramData(
|
|
113
|
+
token,
|
|
114
|
+
"me/content_publishing_limit",
|
|
115
|
+
{ params: { fields: "quota_usage" } }
|
|
116
|
+
);
|
|
117
|
+
if (!Array.isArray(result?.data)) {
|
|
118
|
+
console.error(
|
|
119
|
+
`[naystack] Instagram token cannot publish${result?.error ? `: ${result.error.message}` : ""}`
|
|
120
|
+
);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
};
|
|
78
125
|
export {
|
|
126
|
+
canPublishToInstagram,
|
|
127
|
+
getInstagramContainerStatus,
|
|
79
128
|
getInstagramConversation,
|
|
80
129
|
getInstagramConversationByUser,
|
|
81
130
|
getInstagramConversations,
|
|
@@ -20,35 +20,213 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/socials/instagram/setters.ts
|
|
21
21
|
var setters_exports = {};
|
|
22
22
|
__export(setters_exports, {
|
|
23
|
+
createInstagramPost: () => createInstagramPost,
|
|
23
24
|
sendInstagramMessage: () => sendInstagramMessage
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(setters_exports);
|
|
26
27
|
|
|
28
|
+
// src/socials/meta/request.ts
|
|
29
|
+
function createGraphClient(baseURL) {
|
|
30
|
+
const buildURL = (token, path, params = {}) => {
|
|
31
|
+
const search = new URLSearchParams();
|
|
32
|
+
for (const [key, value] of Object.entries(params)) {
|
|
33
|
+
if (value !== void 0) search.set(key, String(value));
|
|
34
|
+
}
|
|
35
|
+
search.set("access_token", token);
|
|
36
|
+
return `${baseURL}/${path}?${search.toString()}`;
|
|
37
|
+
};
|
|
38
|
+
const request = async (token, path, options = {}) => {
|
|
39
|
+
const response = await fetch(buildURL(token, path, options.params), {
|
|
40
|
+
method: options.method || (options.body ? "POST" : "GET"),
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: `Bearer ${token}`,
|
|
43
|
+
...options.body ? { "Content-Type": "application/json" } : {}
|
|
44
|
+
},
|
|
45
|
+
...options.body ? { body: JSON.stringify(options.body) } : {}
|
|
46
|
+
});
|
|
47
|
+
return response.json().catch(() => null);
|
|
48
|
+
};
|
|
49
|
+
return { buildURL, request };
|
|
50
|
+
}
|
|
51
|
+
function readGraphID(label, result) {
|
|
52
|
+
if (result?.error) {
|
|
53
|
+
console.error(`[naystack] ${label} failed: ${result.error.message}`);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return result?.id || null;
|
|
57
|
+
}
|
|
58
|
+
|
|
27
59
|
// src/socials/instagram/utils.ts
|
|
28
|
-
|
|
29
|
-
|
|
60
|
+
var client = createGraphClient("https://graph.instagram.com/v23.0");
|
|
61
|
+
var getInstagramData = client.request;
|
|
62
|
+
|
|
63
|
+
// src/socials/instagram/getters.ts
|
|
64
|
+
var getInstagramContainerStatus = async (token, id) => {
|
|
65
|
+
const result = await getInstagramData(token, id, { params: { fields: "status_code,status" } });
|
|
66
|
+
if (!result?.status_code) return null;
|
|
67
|
+
return {
|
|
68
|
+
status: result.status_code,
|
|
69
|
+
error: result.status_code === "ERROR" ? result.status : void 0
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/socials/meta/container.ts
|
|
74
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
75
|
+
async function waitForContainer(getState, { intervalMS = 5e3, timeoutMS = 3e5 } = {}) {
|
|
76
|
+
const deadline = Date.now() + timeoutMS;
|
|
77
|
+
let state = await getState();
|
|
78
|
+
if (!state) {
|
|
79
|
+
await sleep(2e3);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
while (state?.status === "IN_PROGRESS" && Date.now() < deadline) {
|
|
83
|
+
await sleep(intervalMS);
|
|
84
|
+
state = await getState();
|
|
85
|
+
}
|
|
86
|
+
return state;
|
|
30
87
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
88
|
+
function createPublisher(platform) {
|
|
89
|
+
const isReady = async (token, id, label, wait) => {
|
|
90
|
+
const state = await waitForContainer(
|
|
91
|
+
() => platform.getStatus(token, id),
|
|
92
|
+
wait
|
|
93
|
+
);
|
|
94
|
+
if (state && state.status !== "FINISHED" && state.status !== "PUBLISHED") {
|
|
95
|
+
console.error(
|
|
96
|
+
`[naystack] ${platform.name} ${label} ${id} is ${state.status}${state.error ? `: ${state.error}` : ""}`
|
|
97
|
+
);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
/** Creates a container, waits for it to finish processing, then publishes it. */
|
|
104
|
+
publish: async (token, params, wait) => {
|
|
105
|
+
const containerID = await platform.createContainer(token, params);
|
|
106
|
+
if (!containerID) return null;
|
|
107
|
+
if (!await isReady(token, containerID, "container", wait)) return null;
|
|
108
|
+
return platform.publish(token, containerID);
|
|
109
|
+
},
|
|
110
|
+
/** Creates and awaits every carousel child, resolving to their ids — or `null` if any failed. */
|
|
111
|
+
createChildren: async (token, items, wait) => {
|
|
112
|
+
const children = [];
|
|
113
|
+
for (const item of items) {
|
|
114
|
+
const childID = await platform.createContainer(token, {
|
|
115
|
+
...item,
|
|
116
|
+
is_carousel_item: true
|
|
117
|
+
});
|
|
118
|
+
if (!childID) return null;
|
|
119
|
+
if (!await isReady(token, childID, "carousel item", wait))
|
|
120
|
+
return null;
|
|
121
|
+
children.push(childID);
|
|
122
|
+
}
|
|
123
|
+
return children;
|
|
38
124
|
}
|
|
39
|
-
}
|
|
125
|
+
};
|
|
40
126
|
}
|
|
41
127
|
|
|
42
128
|
// src/socials/instagram/setters.ts
|
|
129
|
+
var publisher = createPublisher({
|
|
130
|
+
name: "Instagram",
|
|
131
|
+
getStatus: getInstagramContainerStatus,
|
|
132
|
+
createContainer: async (token, params) => readGraphID(
|
|
133
|
+
"Instagram media container",
|
|
134
|
+
await getInstagramData(token, "me/media", {
|
|
135
|
+
params,
|
|
136
|
+
method: "POST"
|
|
137
|
+
})
|
|
138
|
+
),
|
|
139
|
+
publish: async (token, creationID) => readGraphID(
|
|
140
|
+
"Instagram publish",
|
|
141
|
+
await getInstagramData(token, "me/media_publish", {
|
|
142
|
+
params: { creation_id: creationID },
|
|
143
|
+
method: "POST"
|
|
144
|
+
})
|
|
145
|
+
)
|
|
146
|
+
});
|
|
147
|
+
var mediaParams = (media) => ({
|
|
148
|
+
media_type: media.type === "video" /* Video */ ? "VIDEO" : void 0,
|
|
149
|
+
image_url: media.type === "photo" /* Photo */ ? media.url : void 0,
|
|
150
|
+
video_url: media.type === "video" /* Video */ ? media.url : void 0,
|
|
151
|
+
// Instagram accepts alt text on images only.
|
|
152
|
+
alt_text: media.type === "photo" /* Photo */ ? media.altText : void 0
|
|
153
|
+
});
|
|
154
|
+
var createInstagramPost = async (token, input) => {
|
|
155
|
+
const media = [input.media].flat();
|
|
156
|
+
const first = media[0];
|
|
157
|
+
if (!first) {
|
|
158
|
+
console.error(
|
|
159
|
+
"[naystack] Instagram posts need at least one image or video"
|
|
160
|
+
);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
if (input.story) {
|
|
164
|
+
return publisher.publish(
|
|
165
|
+
token,
|
|
166
|
+
{
|
|
167
|
+
...mediaParams(first),
|
|
168
|
+
media_type: "STORIES",
|
|
169
|
+
alt_text: void 0
|
|
170
|
+
},
|
|
171
|
+
input.wait
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (media.length > 1) {
|
|
175
|
+
const children = await publisher.createChildren(
|
|
176
|
+
token,
|
|
177
|
+
media.map(mediaParams),
|
|
178
|
+
input.wait
|
|
179
|
+
);
|
|
180
|
+
if (!children) return null;
|
|
181
|
+
return publisher.publish(
|
|
182
|
+
token,
|
|
183
|
+
{
|
|
184
|
+
media_type: "CAROUSEL",
|
|
185
|
+
children: children.join(","),
|
|
186
|
+
caption: input.caption
|
|
187
|
+
},
|
|
188
|
+
input.wait
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (first.type === "video" /* Video */) {
|
|
192
|
+
return publisher.publish(
|
|
193
|
+
token,
|
|
194
|
+
{
|
|
195
|
+
media_type: "REELS",
|
|
196
|
+
video_url: first.url,
|
|
197
|
+
caption: input.caption,
|
|
198
|
+
cover_url: input.coverURL,
|
|
199
|
+
thumb_offset: input.thumbOffset,
|
|
200
|
+
share_to_feed: input.shareToFeed,
|
|
201
|
+
audio_name: input.audioName,
|
|
202
|
+
collaborators: input.collaborators && JSON.stringify(input.collaborators)
|
|
203
|
+
},
|
|
204
|
+
input.wait
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return publisher.publish(
|
|
208
|
+
token,
|
|
209
|
+
{
|
|
210
|
+
image_url: first.url,
|
|
211
|
+
caption: input.caption,
|
|
212
|
+
alt_text: first.altText,
|
|
213
|
+
location_id: input.locationID
|
|
214
|
+
},
|
|
215
|
+
input.wait
|
|
216
|
+
);
|
|
217
|
+
};
|
|
43
218
|
var sendInstagramMessage = (token, to, text) => {
|
|
44
|
-
return getInstagramData(token, "me/messages",
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
219
|
+
return getInstagramData(token, "me/messages", {
|
|
220
|
+
body: {
|
|
221
|
+
recipient: { id: to },
|
|
222
|
+
message: {
|
|
223
|
+
text
|
|
224
|
+
}
|
|
48
225
|
}
|
|
49
226
|
});
|
|
50
227
|
};
|
|
51
228
|
// Annotate the CommonJS export names for ESM import in node:
|
|
52
229
|
0 && (module.exports = {
|
|
230
|
+
createInstagramPost,
|
|
53
231
|
sendInstagramMessage
|
|
54
232
|
});
|
|
@@ -1,5 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GraphError } from '../meta/types.mjs';
|
|
2
|
+
import { InstagramPostInput } from './types.mjs';
|
|
3
|
+
import '../meta/container.mjs';
|
|
2
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Publishes to Instagram — feed image, reel, story or carousel, chosen from the
|
|
7
|
+
* media you pass. Creates the media container, waits for Instagram to finish
|
|
8
|
+
* processing it, then publishes.
|
|
9
|
+
*
|
|
10
|
+
* Needs an access token with the `instagram_business_content_publish` scope and an
|
|
11
|
+
* Instagram professional account. Media URLs must be publicly reachable (Instagram
|
|
12
|
+
* downloads them server-side) and images must be JPEG. Instagram allows 100
|
|
13
|
+
* published posts per rolling 24 hours.
|
|
14
|
+
*
|
|
15
|
+
* @param token - Instagram access token.
|
|
16
|
+
* @param input - See {@link InstagramPostInput}.
|
|
17
|
+
* @returns Promise of the published media id, or `null` if any step failed (the API's error is logged).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createInstagramPost } from "naystack/socials";
|
|
22
|
+
*
|
|
23
|
+
* // Reel
|
|
24
|
+
* await createInstagramPost(accessToken, {
|
|
25
|
+
* media: { url: "https://cdn.example.com/promo.mp4", type: "video" },
|
|
26
|
+
* caption: "Behind the scenes",
|
|
27
|
+
* shareToFeed: true,
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Carousel
|
|
31
|
+
* await createInstagramPost(accessToken, {
|
|
32
|
+
* caption: "Campaign recap",
|
|
33
|
+
* media: [
|
|
34
|
+
* { url: "https://cdn.example.com/1.jpg", type: "image" },
|
|
35
|
+
* { url: "https://cdn.example.com/2.mp4", type: "video" },
|
|
36
|
+
* ],
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @category Socials
|
|
41
|
+
*/
|
|
42
|
+
declare const createInstagramPost: (token: string, input: InstagramPostInput) => Promise<string | null>;
|
|
3
43
|
/**
|
|
4
44
|
* Sends a text message to an Instagram user via the Instagram Messaging API.
|
|
5
45
|
*
|
|
@@ -21,6 +61,6 @@ import { InstagramError } from './types.mjs';
|
|
|
21
61
|
declare const sendInstagramMessage: (token: string, to: string, text: string) => Promise<({
|
|
22
62
|
recipient_id?: string;
|
|
23
63
|
message_id?: string;
|
|
24
|
-
} &
|
|
64
|
+
} & GraphError) | null>;
|
|
25
65
|
|
|
26
|
-
export { sendInstagramMessage };
|
|
66
|
+
export { createInstagramPost, sendInstagramMessage };
|
|
@@ -1,5 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GraphError } from '../meta/types.js';
|
|
2
|
+
import { InstagramPostInput } from './types.js';
|
|
3
|
+
import '../meta/container.js';
|
|
2
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Publishes to Instagram — feed image, reel, story or carousel, chosen from the
|
|
7
|
+
* media you pass. Creates the media container, waits for Instagram to finish
|
|
8
|
+
* processing it, then publishes.
|
|
9
|
+
*
|
|
10
|
+
* Needs an access token with the `instagram_business_content_publish` scope and an
|
|
11
|
+
* Instagram professional account. Media URLs must be publicly reachable (Instagram
|
|
12
|
+
* downloads them server-side) and images must be JPEG. Instagram allows 100
|
|
13
|
+
* published posts per rolling 24 hours.
|
|
14
|
+
*
|
|
15
|
+
* @param token - Instagram access token.
|
|
16
|
+
* @param input - See {@link InstagramPostInput}.
|
|
17
|
+
* @returns Promise of the published media id, or `null` if any step failed (the API's error is logged).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createInstagramPost } from "naystack/socials";
|
|
22
|
+
*
|
|
23
|
+
* // Reel
|
|
24
|
+
* await createInstagramPost(accessToken, {
|
|
25
|
+
* media: { url: "https://cdn.example.com/promo.mp4", type: "video" },
|
|
26
|
+
* caption: "Behind the scenes",
|
|
27
|
+
* shareToFeed: true,
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Carousel
|
|
31
|
+
* await createInstagramPost(accessToken, {
|
|
32
|
+
* caption: "Campaign recap",
|
|
33
|
+
* media: [
|
|
34
|
+
* { url: "https://cdn.example.com/1.jpg", type: "image" },
|
|
35
|
+
* { url: "https://cdn.example.com/2.mp4", type: "video" },
|
|
36
|
+
* ],
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @category Socials
|
|
41
|
+
*/
|
|
42
|
+
declare const createInstagramPost: (token: string, input: InstagramPostInput) => Promise<string | null>;
|
|
3
43
|
/**
|
|
4
44
|
* Sends a text message to an Instagram user via the Instagram Messaging API.
|
|
5
45
|
*
|
|
@@ -21,6 +61,6 @@ import { InstagramError } from './types.js';
|
|
|
21
61
|
declare const sendInstagramMessage: (token: string, to: string, text: string) => Promise<({
|
|
22
62
|
recipient_id?: string;
|
|
23
63
|
message_id?: string;
|
|
24
|
-
} &
|
|
64
|
+
} & GraphError) | null>;
|
|
25
65
|
|
|
26
|
-
export { sendInstagramMessage };
|
|
66
|
+
export { createInstagramPost, sendInstagramMessage };
|