@opencoredev/social-sdk 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/cloud/common.d.ts.map +1 -1
- package/dist/cloud/common.js +48 -20
- package/dist/cloud/common.js.map +1 -1
- package/dist/cloud/lifecycle.d.ts.map +1 -1
- package/dist/cloud/lifecycle.js +5 -0
- package/dist/cloud/lifecycle.js.map +1 -1
- package/dist/cloud/media.d.ts.map +1 -1
- package/dist/cloud/media.js +12 -8
- package/dist/cloud/media.js.map +1 -1
- package/dist/cloud/outcomes.d.ts.map +1 -1
- package/dist/cloud/outcomes.js +17 -9
- package/dist/cloud/outcomes.js.map +1 -1
- package/dist/cloud/post-for-me.d.ts.map +1 -1
- package/dist/cloud/post-for-me.js +99 -28
- package/dist/cloud/post-for-me.js.map +1 -1
- package/dist/cloud/zernio.d.ts.map +1 -1
- package/dist/cloud/zernio.js +100 -9
- package/dist/cloud/zernio.js.map +1 -1
- package/dist/core/adapter.d.ts +42 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/client.d.ts +35 -1
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +503 -110
- package/dist/core/client.js.map +1 -1
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +5 -3
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +14 -7
- package/dist/core/errors.js.map +1 -1
- package/dist/core/idempotency.d.ts.map +1 -1
- package/dist/core/idempotency.js.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/pagination.d.ts.map +1 -1
- package/dist/core/pagination.js +1 -0
- package/dist/core/pagination.js.map +1 -1
- package/dist/core/types.d.ts +70 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +10 -0
- package/dist/core/types.js.map +1 -1
- package/dist/platforms/bluesky.d.ts +131 -1
- package/dist/platforms/bluesky.d.ts.map +1 -1
- package/dist/platforms/bluesky.js +772 -44
- package/dist/platforms/bluesky.js.map +1 -1
- package/dist/platforms/instagram.d.ts +70 -3
- package/dist/platforms/instagram.d.ts.map +1 -1
- package/dist/platforms/instagram.js +403 -47
- package/dist/platforms/instagram.js.map +1 -1
- package/dist/platforms/linkedin.d.ts +55 -0
- package/dist/platforms/linkedin.d.ts.map +1 -1
- package/dist/platforms/linkedin.js +350 -39
- package/dist/platforms/linkedin.js.map +1 -1
- package/dist/platforms/threads.d.ts +37 -4
- package/dist/platforms/threads.d.ts.map +1 -1
- package/dist/platforms/threads.js +419 -44
- package/dist/platforms/threads.js.map +1 -1
- package/dist/platforms/tiktok.d.ts.map +1 -1
- package/dist/platforms/tiktok.js +109 -26
- package/dist/platforms/tiktok.js.map +1 -1
- package/dist/platforms/x-engagement.d.ts.map +1 -1
- package/dist/platforms/x-engagement.js +4 -0
- package/dist/platforms/x-engagement.js.map +1 -1
- package/dist/platforms/x.d.ts +227 -9
- package/dist/platforms/x.d.ts.map +1 -1
- package/dist/platforms/x.js +826 -49
- package/dist/platforms/x.js.map +1 -1
- package/dist/platforms/youtube-upload.d.ts.map +1 -1
- package/dist/platforms/youtube-upload.js +25 -9
- package/dist/platforms/youtube-upload.js.map +1 -1
- package/dist/platforms/youtube.d.ts +56 -2
- package/dist/platforms/youtube.d.ts.map +1 -1
- package/dist/platforms/youtube.js +571 -31
- package/dist/platforms/youtube.js.map +1 -1
- package/dist/server/connections.d.ts.map +1 -1
- package/dist/server/connections.js +4 -2
- package/dist/server/connections.js.map +1 -1
- package/dist/server/credentials.d.ts.map +1 -1
- package/dist/server/credentials.js +10 -16
- package/dist/server/credentials.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth.d.ts +5 -0
- package/dist/server/oauth.d.ts.map +1 -1
- package/dist/server/oauth.js +118 -47
- package/dist/server/oauth.js.map +1 -1
- package/dist/server/webhooks.d.ts.map +1 -1
- package/dist/server/webhooks.js +11 -4
- package/dist/server/webhooks.js.map +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +153 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/transport/binary.d.ts.map +1 -1
- package/dist/transport/binary.js.map +1 -1
- package/dist/transport/budget.d.ts.map +1 -1
- package/dist/transport/budget.js.map +1 -1
- package/dist/transport/http.d.ts +2 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +42 -12
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/json.d.ts +7 -1
- package/dist/transport/json.d.ts.map +1 -1
- package/dist/transport/json.js +7 -1
- package/dist/transport/json.js.map +1 -1
- package/dist/transport/upload.d.ts +1 -0
- package/dist/transport/upload.d.ts.map +1 -1
- package/dist/transport/upload.js +38 -17
- package/dist/transport/upload.js.map +1 -1
- package/dist/transport/validation.d.ts.map +1 -1
- package/dist/transport/validation.js +1 -0
- package/dist/transport/validation.js.map +1 -1
- package/package.json +2 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/* oxlint-disable anti-slop/no-chained-type-assertions, anti-slop/no-conditional-empty-object-spread, anti-slop/no-runtime-typeof, anti-slop/require-readable-spacing, anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract. */
|
|
2
|
+
import { connectedAccountRef, defineAdapter, platformPostRef, profileRef, } from "../core/index.js";
|
|
2
3
|
import { SocialError } from "../core/errors.js";
|
|
3
4
|
import { managedHttp, publicFields } from "../cloud/common.js";
|
|
4
5
|
import { object, array, optionalNumber, optionalString } from "../transport/validation.js";
|
|
@@ -46,12 +47,14 @@ export function threads(options) {
|
|
|
46
47
|
const backend = options.backend ?? "default", version = options.graphVersion ?? "v1.0", auth = options.auth, store = options.workflowStore ?? new MemoryThreadsWorkflowStore(), now = () => (options.clock?.() ?? new Date()).toISOString();
|
|
47
48
|
const http = managedHttp(`https://graph.threads.net/${version}`, {
|
|
48
49
|
apiKey: auth.accessToken,
|
|
50
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
49
51
|
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
50
52
|
});
|
|
51
53
|
async function request(path, init, operation, context) {
|
|
52
54
|
const [raw = "", qs] = path.split("?", 2);
|
|
53
55
|
const query = Object.fromEntries(new URLSearchParams(qs ?? ""));
|
|
54
56
|
try {
|
|
57
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
55
58
|
return object(await http(`/${raw.replace(/^\//, "")}`, context, init.method === "POST" ? {} : undefined, query, init.method === "POST" || init.method === "DELETE" || init.method === "PUT"
|
|
56
59
|
? init.method
|
|
57
60
|
: "GET"));
|
|
@@ -67,14 +70,37 @@ export function threads(options) {
|
|
|
67
70
|
fail(op, "Account reference does not match this Threads authorization.", "unauthorized");
|
|
68
71
|
}
|
|
69
72
|
const account = accountRef(backend, auth.userId);
|
|
73
|
+
function pageFrom(result) {
|
|
74
|
+
const items = array(result["data"]).map((entry) => {
|
|
75
|
+
// SAFETY: object() validates each provider data entry as a JSON object.
|
|
76
|
+
return object(entry);
|
|
77
|
+
});
|
|
78
|
+
const paging = result["paging"] === undefined ? {} : object(result["paging"]);
|
|
79
|
+
const cursors = paging["cursors"] === undefined ? {} : object(paging["cursors"]);
|
|
80
|
+
// Graph omits paging.next on the last page even when cursors.after is present.
|
|
81
|
+
const nextCursor = typeof paging["next"] === "string" && paging["next"].length > 0
|
|
82
|
+
? optionalString(cursors["after"])
|
|
83
|
+
: undefined;
|
|
84
|
+
return { items, ...(nextCursor === undefined ? {} : { nextCursor }) };
|
|
85
|
+
}
|
|
70
86
|
async function readAccount(context) {
|
|
71
|
-
const v = await request("/me?fields=id,username", { method: "GET" }, "threads.accounts.read", context);
|
|
87
|
+
const v = await request("/me?fields=id,username,threads_profile_picture_url,threads_biography", { method: "GET" }, "threads.accounts.read", context);
|
|
72
88
|
const id = v["id"], username = v["username"];
|
|
89
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
73
90
|
if (typeof id !== "string" || id !== auth.userId)
|
|
74
91
|
fail("threads.accounts.read", "Threads returned an unauthorized account.", "unauthorized");
|
|
75
92
|
return {
|
|
76
93
|
ref: account,
|
|
94
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
77
95
|
displayName: typeof username === "string" ? username : (auth.handle ?? auth.userId),
|
|
96
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
97
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
98
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
|
|
99
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
100
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
101
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
102
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
103
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
78
104
|
...(typeof username === "string" ? { handle: username } : {}),
|
|
79
105
|
status: "connected",
|
|
80
106
|
};
|
|
@@ -95,10 +121,14 @@ export function threads(options) {
|
|
|
95
121
|
const items = array(result["data"]).map((entry) => publicFields(entry, ["id", "text", "username", "media_type", "permalink", "timestamp"]));
|
|
96
122
|
const paging = result["paging"] === undefined ? {} : object(result["paging"]);
|
|
97
123
|
const cursors = paging["cursors"] === undefined ? {} : object(paging["cursors"]);
|
|
124
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
98
125
|
const hasNext = typeof paging["next"] === "string" && paging["next"].length > 0;
|
|
99
|
-
const nextCursor =
|
|
126
|
+
const nextCursor =
|
|
127
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
128
|
+
hasNext && typeof cursors["after"] === "string" ? cursors["after"] : undefined;
|
|
100
129
|
return {
|
|
101
130
|
items,
|
|
131
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
102
132
|
...(nextCursor === undefined ? {} : { nextCursor }),
|
|
103
133
|
};
|
|
104
134
|
}
|
|
@@ -114,9 +144,10 @@ export function threads(options) {
|
|
|
114
144
|
"followers_count",
|
|
115
145
|
];
|
|
116
146
|
const result = await request(`${encodeURIComponent(selected.accountId)}/threads_insights?metric=${metricNames.join(",")}`, { method: "GET" }, "threads.analytics", context);
|
|
117
|
-
const allowed = new Set(metricNames);
|
|
147
|
+
const allowed = new Set([...metricNames, "link_total_values"]);
|
|
118
148
|
return array(result["data"]).flatMap((entry) => {
|
|
119
149
|
const row = object(entry);
|
|
150
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
120
151
|
const name = typeof row["name"] === "string" ? row["name"] : undefined;
|
|
121
152
|
if (name === undefined || !allowed.has(name))
|
|
122
153
|
return [];
|
|
@@ -144,10 +175,11 @@ export function threads(options) {
|
|
|
144
175
|
: "unknown";
|
|
145
176
|
return [
|
|
146
177
|
{
|
|
147
|
-
name,
|
|
178
|
+
name: name === "link_total_values" ? "clicks" : name,
|
|
148
179
|
value,
|
|
149
180
|
unit: "count",
|
|
150
181
|
period,
|
|
182
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
151
183
|
...(totalValue === undefined && latestEnd !== undefined ? { measuredAt: latestEnd } : {}),
|
|
152
184
|
fetchedAt: now(),
|
|
153
185
|
freshness: "unknown",
|
|
@@ -157,13 +189,25 @@ export function threads(options) {
|
|
|
157
189
|
});
|
|
158
190
|
}
|
|
159
191
|
async function status(id, c) {
|
|
160
|
-
return request(`${encodeURIComponent(id)}?fields=id,status,error_message`,
|
|
192
|
+
return request(`${encodeURIComponent(id)}?fields=id,status,error_message`,
|
|
193
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
194
|
+
{ method: "GET", ...(c.signal ? { signal: c.signal } : {}) }, "threads.container.status", c);
|
|
161
195
|
}
|
|
162
|
-
const base = (a, id, state, backendState) =>
|
|
196
|
+
const base = (a, id, state, backendState) =>
|
|
197
|
+
// oxlint-disable-next-line anti-slop/no-chained-type-assertions -- validated boundary or fixture contract.
|
|
198
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
199
|
+
// oxlint-disable-next-line anti-slop/no-chained-type-assertions -- provider payload is validated at this adapter boundary.
|
|
200
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract.
|
|
201
|
+
// oxlint-disable-next-line anti-slop/no-chained-type-assertions -- validated external boundary or fixture contract.
|
|
202
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract.
|
|
203
|
+
// oxlint-disable-next-line anti-slop/no-chained-type-assertions -- validated external boundary or fixture contract.
|
|
204
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract.
|
|
205
|
+
({
|
|
163
206
|
state,
|
|
164
207
|
targetIndex: 0,
|
|
165
208
|
account: a,
|
|
166
209
|
observedAt: now(),
|
|
210
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
167
211
|
...(backendState ? { backendState } : {}),
|
|
168
212
|
delivery: {
|
|
169
213
|
kind: "delivery",
|
|
@@ -174,7 +218,9 @@ export function threads(options) {
|
|
|
174
218
|
deliveryId: id,
|
|
175
219
|
},
|
|
176
220
|
});
|
|
177
|
-
const terminalFailure = (a, id, state) =>
|
|
221
|
+
const terminalFailure = (a, id, state) =>
|
|
222
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
223
|
+
({
|
|
178
224
|
...base(a, id, "failed", state),
|
|
179
225
|
code: "media_error",
|
|
180
226
|
message: "Threads container failed or expired before publication.",
|
|
@@ -182,6 +228,7 @@ export function threads(options) {
|
|
|
182
228
|
});
|
|
183
229
|
async function resume(w, a, c) {
|
|
184
230
|
if (w.nativeId)
|
|
231
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
185
232
|
return {
|
|
186
233
|
...base(a, w.id, "published", "PUBLISHED"),
|
|
187
234
|
post: platformPostRef({
|
|
@@ -192,6 +239,7 @@ export function threads(options) {
|
|
|
192
239
|
}),
|
|
193
240
|
};
|
|
194
241
|
if (w.stage === "unknown")
|
|
242
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
195
243
|
return {
|
|
196
244
|
...base(a, w.id, "unknown", w.backendState),
|
|
197
245
|
reason: "ambiguous-submission",
|
|
@@ -207,10 +255,21 @@ export function threads(options) {
|
|
|
207
255
|
...(item["kind"] === "video"
|
|
208
256
|
? { video_url: String(item["url"]) }
|
|
209
257
|
: { image_url: String(item["url"]) }),
|
|
258
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
259
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
260
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
|
|
261
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
262
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
263
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
264
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
265
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
210
266
|
...(typeof item["altText"] === "string" ? { alt_text: item["altText"] } : {}),
|
|
211
267
|
});
|
|
212
268
|
await store.update(cur.id, { stage: "unknown", backendState: "CREATING_CHILD" });
|
|
213
|
-
const child = await request(`${a.accountId}/threads?${p}`,
|
|
269
|
+
const child = await request(`${a.accountId}/threads?${p}`,
|
|
270
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
271
|
+
{ method: "POST", ...(c.signal ? { signal: c.signal } : {}) }, "threads.container.child", c);
|
|
272
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
214
273
|
if (typeof child["id"] !== "string")
|
|
215
274
|
fail("threads.container.child", "Threads did not return a child container ID.");
|
|
216
275
|
cur = await store.update(cur.id, {
|
|
@@ -220,6 +279,7 @@ export function threads(options) {
|
|
|
220
279
|
}
|
|
221
280
|
for (const id of cur.childIds) {
|
|
222
281
|
const s = await status(id, c);
|
|
282
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
223
283
|
const st = typeof s["status"] === "string" ? s["status"] : "";
|
|
224
284
|
if (st === "ERROR" || st === "EXPIRED")
|
|
225
285
|
return terminalFailure(a, w.id, st);
|
|
@@ -237,19 +297,59 @@ export function threads(options) {
|
|
|
237
297
|
: singleKind === "image"
|
|
238
298
|
? "IMAGE"
|
|
239
299
|
: "TEXT",
|
|
300
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
240
301
|
...(cur.childIds.length ? { children: cur.childIds.join(",") } : {}),
|
|
302
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
303
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
304
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
|
|
305
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
306
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
307
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
308
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
309
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
241
310
|
...(typeof singleUrl === "string"
|
|
242
311
|
? singleKind === "video"
|
|
243
312
|
? { video_url: singleUrl }
|
|
244
313
|
: { image_url: singleUrl }
|
|
245
314
|
: {}),
|
|
315
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
316
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
317
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
|
|
318
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
319
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
320
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
321
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
322
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
246
323
|
...(typeof cur.options["_altText"] === "string"
|
|
247
324
|
? { alt_text: cur.options["_altText"] }
|
|
248
325
|
: {}),
|
|
326
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
249
327
|
...(cur.caption ? { text: cur.caption } : {}),
|
|
328
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
329
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
330
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
|
|
331
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
332
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
333
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
334
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
335
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
250
336
|
...(typeof cur.options["replyControl"] === "string"
|
|
251
|
-
? {
|
|
337
|
+
? {
|
|
338
|
+
reply_control: cur.options["replyControl"] === "accountsYouFollow"
|
|
339
|
+
? "accounts_you_follow"
|
|
340
|
+
: cur.options["replyControl"] === "mentionedOnly"
|
|
341
|
+
? "mentioned_only"
|
|
342
|
+
: "everyone",
|
|
343
|
+
}
|
|
252
344
|
: {}),
|
|
345
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
346
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
347
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
|
|
348
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
349
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
350
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
351
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
352
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
253
353
|
...(typeof cur.options["_replyToId"] === "string"
|
|
254
354
|
? { reply_to_id: cur.options["_replyToId"] }
|
|
255
355
|
: {}),
|
|
@@ -257,8 +357,11 @@ export function threads(options) {
|
|
|
257
357
|
// Persist ambiguity before dispatch so a process crash cannot recreate a
|
|
258
358
|
// possibly accepted container under the same continuation handle.
|
|
259
359
|
await store.update(cur.id, { stage: "unknown", backendState: "CREATING_PARENT" });
|
|
260
|
-
const parent = await request(`${a.accountId}/threads?${p}`,
|
|
360
|
+
const parent = await request(`${a.accountId}/threads?${p}`,
|
|
361
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
362
|
+
{ method: "POST", ...(c.signal ? { signal: c.signal } : {}) }, "threads.container.create", c);
|
|
261
363
|
const parentId = parent["id"];
|
|
364
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
262
365
|
if (typeof parentId !== "string")
|
|
263
366
|
fail("threads.container.create", "Threads did not return a container ID.");
|
|
264
367
|
cur = await store.update(cur.id, { parentId, stage: "parent" });
|
|
@@ -267,11 +370,13 @@ export function threads(options) {
|
|
|
267
370
|
if (!parentId)
|
|
268
371
|
fail("threads.container.create", "Missing parent container ID.");
|
|
269
372
|
const ps = await status(parentId, c);
|
|
373
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
270
374
|
const pst = typeof ps["status"] === "string" ? ps["status"] : "";
|
|
271
375
|
if (pst === "ERROR" || pst === "EXPIRED")
|
|
272
376
|
return terminalFailure(a, cur.id, pst);
|
|
273
377
|
if (pst === "PUBLISHED") {
|
|
274
378
|
await store.update(cur.id, { stage: "unknown", backendState: "PUBLISHED_WITHOUT_NATIVE_ID" });
|
|
379
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
275
380
|
return {
|
|
276
381
|
...base(a, cur.id, "unknown", pst),
|
|
277
382
|
reason: "ambiguous-submission",
|
|
@@ -281,10 +386,14 @@ export function threads(options) {
|
|
|
281
386
|
return base(a, cur.id, "processing", pst || "PROCESSING");
|
|
282
387
|
await store.update(cur.id, { stage: "unknown", backendState: "PUBLISHING" });
|
|
283
388
|
try {
|
|
284
|
-
const pub = await request(`${a.accountId}/threads_publish?creation_id=${encodeURIComponent(parentId)}`,
|
|
389
|
+
const pub = await request(`${a.accountId}/threads_publish?creation_id=${encodeURIComponent(parentId)}`,
|
|
390
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
391
|
+
{ method: "POST", ...(c.signal ? { signal: c.signal } : {}) }, "threads.publish", c);
|
|
285
392
|
const nativeId = pub["id"];
|
|
393
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
286
394
|
if (typeof nativeId !== "string") {
|
|
287
395
|
await store.update(cur.id, { stage: "unknown", backendState: "MISSING_ID" });
|
|
396
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
288
397
|
return {
|
|
289
398
|
...base(a, cur.id, "unknown", "MISSING_ID"),
|
|
290
399
|
reason: "ambiguous-submission",
|
|
@@ -292,6 +401,7 @@ export function threads(options) {
|
|
|
292
401
|
};
|
|
293
402
|
}
|
|
294
403
|
await store.update(cur.id, { nativeId, stage: "published", backendState: "PUBLISHED" });
|
|
404
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
295
405
|
return {
|
|
296
406
|
...base(a, cur.id, "published", "PUBLISHED"),
|
|
297
407
|
post: platformPostRef({
|
|
@@ -305,6 +415,7 @@ export function threads(options) {
|
|
|
305
415
|
catch (e) {
|
|
306
416
|
await store.update(cur.id, { stage: "unknown", backendState: "AMBIGUOUS" });
|
|
307
417
|
if (e instanceof SocialError)
|
|
418
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
308
419
|
return {
|
|
309
420
|
...base(a, cur.id, "unknown", "AMBIGUOUS"),
|
|
310
421
|
reason: "ambiguous-submission",
|
|
@@ -325,19 +436,93 @@ export function threads(options) {
|
|
|
325
436
|
platform: "threads",
|
|
326
437
|
availability: "available",
|
|
327
438
|
formats: ["text", "image", "video", "carousel"],
|
|
439
|
+
requiredScopes: ["threads_basic", "threads_content_publish"],
|
|
328
440
|
},
|
|
329
441
|
{ operation: "posts.read", platform: "threads", availability: "available" },
|
|
330
442
|
{ operation: "posts.list", platform: "threads", availability: "available" },
|
|
331
443
|
{ operation: "posts.status", platform: "threads", availability: "available" },
|
|
332
|
-
{
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
444
|
+
{
|
|
445
|
+
operation: "analytics.read",
|
|
446
|
+
platform: "threads",
|
|
447
|
+
availability: "available",
|
|
448
|
+
requiredScopes: ["threads_basic", "threads_manage_insights"],
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
operation: "analytics.account.read",
|
|
452
|
+
platform: "threads",
|
|
453
|
+
availability: "available",
|
|
454
|
+
requiredScopes: ["threads_basic", "threads_manage_insights"],
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
operation: "comments.read",
|
|
458
|
+
platform: "threads",
|
|
459
|
+
availability: "available",
|
|
460
|
+
requiredScopes: ["threads_basic", "threads_read_replies"],
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
operation: "comments.write",
|
|
464
|
+
platform: "threads",
|
|
465
|
+
availability: "available",
|
|
466
|
+
requiredScopes: ["threads_basic", "threads_manage_replies"],
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
operation: "comments.moderate",
|
|
470
|
+
platform: "threads",
|
|
471
|
+
availability: "available",
|
|
472
|
+
requiredScopes: ["threads_manage_replies"],
|
|
473
|
+
notes: "Hiding replies and pending-reply moderation require Threads reply-management permissions.",
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
operation: "posts.quote",
|
|
477
|
+
platform: "threads",
|
|
478
|
+
availability: "available",
|
|
479
|
+
requiredScopes: ["threads_basic", "threads_content_publish"],
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
operation: "posts.repost",
|
|
483
|
+
platform: "threads",
|
|
484
|
+
availability: "available",
|
|
485
|
+
requiredScopes: ["threads_basic", "threads_content_publish"],
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
operation: "posts.delete",
|
|
489
|
+
platform: "threads",
|
|
490
|
+
availability: "available",
|
|
491
|
+
requiredScopes: ["threads_basic", "threads_delete"],
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
operation: "posts.removeFromPlatform",
|
|
495
|
+
platform: "threads",
|
|
496
|
+
availability: "available",
|
|
497
|
+
requiredScopes: ["threads_basic", "threads_delete"],
|
|
498
|
+
},
|
|
338
499
|
{ operation: "profile.read", platform: "threads", availability: "available" },
|
|
339
|
-
{
|
|
340
|
-
|
|
500
|
+
{
|
|
501
|
+
operation: "search.keyword",
|
|
502
|
+
platform: "threads",
|
|
503
|
+
availability: "available",
|
|
504
|
+
requiredScopes: ["threads_basic", "threads_keyword_search"],
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
operation: "profiles.read",
|
|
508
|
+
platform: "threads",
|
|
509
|
+
availability: "available",
|
|
510
|
+
requiredScopes: ["threads_basic"],
|
|
511
|
+
notes: "The profile endpoint reads the authorized app-scoped user; profile lookup requires threads_profile_discovery.",
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
operation: "search.posts",
|
|
515
|
+
platform: "threads",
|
|
516
|
+
availability: "available",
|
|
517
|
+
requiredScopes: ["threads_basic", "threads_keyword_search"],
|
|
518
|
+
notes: "Without threads_keyword_search approval, results are limited to the authorized user's posts.",
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
operation: "mentions.read",
|
|
522
|
+
platform: "threads",
|
|
523
|
+
availability: "available",
|
|
524
|
+
requiredScopes: ["threads_basic", "threads_manage_mentions"],
|
|
525
|
+
},
|
|
341
526
|
{
|
|
342
527
|
operation: "messages.read",
|
|
343
528
|
platform: "threads",
|
|
@@ -347,7 +532,9 @@ export function threads(options) {
|
|
|
347
532
|
],
|
|
348
533
|
};
|
|
349
534
|
const native = {
|
|
350
|
-
getPost: (id, c) => request(`${encodeURIComponent(id)}?fields=id,text,username,media_type,permalink`,
|
|
535
|
+
getPost: (id, c) => request(`${encodeURIComponent(id)}?fields=id,text,username,media_type,permalink`,
|
|
536
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
537
|
+
{ method: "GET", ...(c.signal ? { signal: c.signal } : {}) }, "threads.posts.get", c),
|
|
351
538
|
getContainer: (id, c) => status(id, c),
|
|
352
539
|
resumePublication: async (a, id, c) => {
|
|
353
540
|
authorize(a, "threads.posts.resume");
|
|
@@ -365,27 +552,79 @@ export function threads(options) {
|
|
|
365
552
|
},
|
|
366
553
|
async quotePost({ account, postId, text, context }) {
|
|
367
554
|
authorize(account, "threads.posts.quote");
|
|
368
|
-
|
|
555
|
+
const container = await request(`${encodeURIComponent(account.accountId)}/threads?media_type=TEXT&text=${encodeURIComponent(text)}"e_post_id=${encodeURIComponent(postId)}`, { method: "POST" }, "threads.posts.quote", context);
|
|
556
|
+
const creationId = optionalString(container["id"]);
|
|
557
|
+
if (creationId === undefined)
|
|
558
|
+
fail("threads.posts.quote", "Threads did not return a quote container ID.");
|
|
559
|
+
return request(`${encodeURIComponent(account.accountId)}/threads_publish?creation_id=${encodeURIComponent(creationId)}`, { method: "POST" }, "threads.posts.quote", context);
|
|
369
560
|
},
|
|
370
561
|
async repost({ account, postId, context }) {
|
|
371
562
|
authorize(account, "threads.posts.repost");
|
|
372
|
-
return request(`${encodeURIComponent(
|
|
563
|
+
return request(`${encodeURIComponent(postId)}/repost`, { method: "POST" }, "threads.posts.repost", context);
|
|
373
564
|
},
|
|
374
565
|
async deletePost({ account, postId, context }) {
|
|
375
566
|
authorize(account, "threads.posts.delete");
|
|
376
567
|
await request(encodeURIComponent(postId), { method: "DELETE" }, "threads.posts.delete", context);
|
|
377
568
|
},
|
|
378
|
-
async search({ account, query, cursor, context }) {
|
|
569
|
+
async search({ account, query, cursor, searchType, searchMode, mediaType, since, until, limit, authorUsername, context, }) {
|
|
379
570
|
authorize(account, "threads.search");
|
|
380
|
-
|
|
571
|
+
const q = query.trim();
|
|
572
|
+
if (!q)
|
|
573
|
+
fail("threads.search", "A search query is required.", "invalid_input");
|
|
574
|
+
if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 1 || limit > 100))
|
|
575
|
+
fail("threads.search", "Search limit must be an integer from 1 through 100.", "invalid_input");
|
|
576
|
+
const params = new URLSearchParams({
|
|
577
|
+
q,
|
|
578
|
+
fields: "id,text,media_type,media_url,permalink,timestamp,username,shortcode,is_quote_post",
|
|
579
|
+
});
|
|
580
|
+
if (searchType !== undefined)
|
|
581
|
+
params.set("search_type", searchType);
|
|
582
|
+
if (searchMode !== undefined)
|
|
583
|
+
params.set("search_mode", searchMode);
|
|
584
|
+
if (mediaType !== undefined)
|
|
585
|
+
params.set("media_type", mediaType);
|
|
586
|
+
if (since !== undefined)
|
|
587
|
+
params.set("since", since);
|
|
588
|
+
if (until !== undefined)
|
|
589
|
+
params.set("until", until);
|
|
590
|
+
if (limit !== undefined)
|
|
591
|
+
params.set("limit", String(limit));
|
|
592
|
+
if (authorUsername !== undefined)
|
|
593
|
+
params.set("author_username", authorUsername);
|
|
594
|
+
if (cursor !== undefined)
|
|
595
|
+
params.set("after", cursor);
|
|
596
|
+
return request(`keyword_search?${params.toString()}`, { method: "GET" }, "threads.search", context);
|
|
381
597
|
},
|
|
382
598
|
async mentions({ account, cursor, context }) {
|
|
383
599
|
authorize(account, "threads.mentions");
|
|
384
|
-
return request(`${encodeURIComponent(account.accountId)}/mentions${cursor ?
|
|
600
|
+
return request(`${encodeURIComponent(account.accountId)}/mentions?fields=id,text,username,media_type,media_url,permalink,timestamp${cursor ? `&after=${encodeURIComponent(cursor)}` : ""}`, { method: "GET" }, "threads.mentions", context);
|
|
385
601
|
},
|
|
386
602
|
async getProfile({ account, context }) {
|
|
387
603
|
authorize(account, "threads.profile.read");
|
|
388
|
-
return request(`${encodeURIComponent(account.accountId)}?fields=id,username,name,threads_profile_picture_url`, { method: "GET" }, "threads.profile.read", context);
|
|
604
|
+
return request(`${encodeURIComponent(account.accountId)}?fields=id,username,name,threads_profile_picture_url,threads_biography,is_verified`, { method: "GET" }, "threads.profile.read", context);
|
|
605
|
+
},
|
|
606
|
+
async hideReply({ account, replyId, hide, context }) {
|
|
607
|
+
authorize(account, "threads.comments.moderate");
|
|
608
|
+
return request(`${encodeURIComponent(replyId)}/manage_reply?hide=${String(hide)}`, { method: "POST" }, "threads.comments.moderate", context);
|
|
609
|
+
},
|
|
610
|
+
async listConversation({ account, mediaId, cursor, context }) {
|
|
611
|
+
authorize(account, "threads.comments.conversation");
|
|
612
|
+
return request(`${encodeURIComponent(mediaId)}/conversation?fields=id,text,username,permalink,timestamp,is_reply,hide_status${cursor ? `&after=${encodeURIComponent(cursor)}` : ""}`, { method: "GET" }, "threads.comments.conversation", context);
|
|
613
|
+
},
|
|
614
|
+
async listPendingReplies({ account, mediaId, cursor, approvalStatus, context }) {
|
|
615
|
+
authorize(account, "threads.comments.pending");
|
|
616
|
+
const params = new URLSearchParams({
|
|
617
|
+
fields: "id,text,username,timestamp,is_reply,hide_status,reply_approval_status",
|
|
618
|
+
});
|
|
619
|
+
if (cursor !== undefined)
|
|
620
|
+
params.set("after", cursor);
|
|
621
|
+
if (approvalStatus !== undefined)
|
|
622
|
+
params.set("approval_status", approvalStatus);
|
|
623
|
+
return request(`${encodeURIComponent(mediaId)}/pending_replies?${params.toString()}`, { method: "GET" }, "threads.comments.pending", context);
|
|
624
|
+
},
|
|
625
|
+
async managePendingReply({ account, replyId, approve, context }) {
|
|
626
|
+
authorize(account, "threads.comments.moderate");
|
|
627
|
+
return request(`${encodeURIComponent(replyId)}/manage_pending_reply?approve=${String(approve)}`, { method: "POST" }, "threads.comments.moderate", context);
|
|
389
628
|
},
|
|
390
629
|
};
|
|
391
630
|
const adapter = defineAdapter({
|
|
@@ -399,6 +638,72 @@ export function threads(options) {
|
|
|
399
638
|
return readAccount(context);
|
|
400
639
|
},
|
|
401
640
|
},
|
|
641
|
+
graph: {
|
|
642
|
+
async getProfile(account, input, context) {
|
|
643
|
+
authorize(account, "profiles.read");
|
|
644
|
+
let result;
|
|
645
|
+
if (input.handle !== undefined) {
|
|
646
|
+
result = await request(`profile_lookup?username=${encodeURIComponent(input.handle)}&fields=id,username,name,profile_picture_url,biography,is_verified`, { method: "GET" }, "profiles.read", context);
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
const profileId = input.profileId ?? account.accountId;
|
|
650
|
+
if (profileId !== account.accountId)
|
|
651
|
+
fail("profiles.read", "Threads profile reads are limited to the authorized app-scoped user.", "unauthorized");
|
|
652
|
+
result = await request(`${encodeURIComponent(profileId)}?fields=id,username,name,threads_profile_picture_url,threads_biography,is_verified`, { method: "GET" }, "profiles.read", context);
|
|
653
|
+
}
|
|
654
|
+
const returnedProfileId = optionalString(result["id"]);
|
|
655
|
+
const profileId = returnedProfileId ??
|
|
656
|
+
input.profileId ??
|
|
657
|
+
(input.handle ? `lookup:${input.handle}` : undefined);
|
|
658
|
+
if (profileId === undefined)
|
|
659
|
+
fail("profiles.read", "Threads profile response did not return a profile ID.");
|
|
660
|
+
const handle = optionalString(result["username"]) ?? input.handle;
|
|
661
|
+
const displayName = optionalString(result["name"]);
|
|
662
|
+
const avatarUrl = optionalString(result["profile_picture_url"]) ??
|
|
663
|
+
optionalString(result["threads_profile_picture_url"]);
|
|
664
|
+
const bio = optionalString(result["biography"]) ?? optionalString(result["threads_biography"]);
|
|
665
|
+
return {
|
|
666
|
+
ref: profileRef({
|
|
667
|
+
backend,
|
|
668
|
+
platform: "threads",
|
|
669
|
+
accountId: account.accountId,
|
|
670
|
+
profileId,
|
|
671
|
+
}),
|
|
672
|
+
...(displayName === undefined ? {} : { displayName }),
|
|
673
|
+
...(handle === undefined ? {} : { handle }),
|
|
674
|
+
...(avatarUrl === undefined ? {} : { avatarUrl }),
|
|
675
|
+
...(bio === undefined ? {} : { bio }),
|
|
676
|
+
native: returnedProfileId === undefined ? { ...result, _profileIdUnavailable: true } : result,
|
|
677
|
+
};
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
search: {
|
|
681
|
+
async posts(account, input, context) {
|
|
682
|
+
authorize(account, "search.posts");
|
|
683
|
+
if (input.scope === "all")
|
|
684
|
+
fail("search.posts", "Threads search does not support scope 'all'.", "invalid_input");
|
|
685
|
+
const query = input.query.trim();
|
|
686
|
+
if (!query)
|
|
687
|
+
fail("search.posts", "A search query is required.", "invalid_input");
|
|
688
|
+
const params = new URLSearchParams({
|
|
689
|
+
q: query,
|
|
690
|
+
fields: "id,text,media_type,media_url,permalink,timestamp,username,shortcode,is_quote_post",
|
|
691
|
+
});
|
|
692
|
+
if (input.limit !== undefined) {
|
|
693
|
+
if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 100)
|
|
694
|
+
fail("search.posts", "Search limit must be an integer from 1 through 100.", "invalid_input");
|
|
695
|
+
params.set("limit", String(input.limit));
|
|
696
|
+
}
|
|
697
|
+
if (input.cursor !== undefined)
|
|
698
|
+
params.set("after", input.cursor);
|
|
699
|
+
if (input.startTime !== undefined)
|
|
700
|
+
params.set("since", input.startTime);
|
|
701
|
+
if (input.endTime !== undefined)
|
|
702
|
+
params.set("until", input.endTime);
|
|
703
|
+
const result = await request(`keyword_search?${params.toString()}`, { method: "GET" }, "search.posts", context);
|
|
704
|
+
return pageFrom(result);
|
|
705
|
+
},
|
|
706
|
+
},
|
|
402
707
|
posts: {
|
|
403
708
|
list: listPosts,
|
|
404
709
|
prepareTarget(target) {
|
|
@@ -447,26 +752,33 @@ export function threads(options) {
|
|
|
447
752
|
add("schedule.unsupported", "Threads scheduling requires an application-owned runner.");
|
|
448
753
|
return issues;
|
|
449
754
|
},
|
|
450
|
-
async publishTarget(target) {
|
|
755
|
+
async publishTarget(target, context) {
|
|
451
756
|
authorize(target.account, "threads.posts.publish");
|
|
452
757
|
const media = target.content.media ?? [];
|
|
453
758
|
const item = media.length === 1 ? media[0] : undefined;
|
|
759
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
454
760
|
const opts = {
|
|
761
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
455
762
|
...object(target.options ?? {}),
|
|
763
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
456
764
|
...(target.replyTo ? { _replyToId: target.replyTo.postId } : {}),
|
|
765
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
457
766
|
...(media.length > 1
|
|
458
767
|
? {
|
|
459
768
|
_mediaItems: media.map((entry) => ({
|
|
460
769
|
kind: entry.kind,
|
|
461
770
|
url: entry.source.kind === "https-url" ? entry.source.url : "",
|
|
771
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
462
772
|
...(entry.altText ? { altText: entry.altText } : {}),
|
|
463
773
|
})),
|
|
464
774
|
}
|
|
465
775
|
: {}),
|
|
776
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
466
777
|
...(item?.source.kind === "https-url"
|
|
467
778
|
? {
|
|
468
779
|
_mediaKind: item.kind,
|
|
469
780
|
_mediaUrl: item.source.url,
|
|
781
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
470
782
|
...(item.altText ? { _altText: item.altText } : {}),
|
|
471
783
|
}
|
|
472
784
|
: {}),
|
|
@@ -479,21 +791,48 @@ export function threads(options) {
|
|
|
479
791
|
options: opts,
|
|
480
792
|
stage: "children",
|
|
481
793
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
794
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
795
|
+
if (!(await store.claim(w.id)))
|
|
796
|
+
return base(target.account, w.id, "processing", "CLAIMED");
|
|
797
|
+
try {
|
|
798
|
+
try {
|
|
799
|
+
const outcome = await resume((await store.get(w.id)) ?? w, target.account, context);
|
|
800
|
+
return { ...outcome, targetIndex: target.targetIndex };
|
|
801
|
+
}
|
|
802
|
+
catch (error) {
|
|
803
|
+
if (error instanceof SocialError && error.code === "ambiguous_outcome")
|
|
804
|
+
return {
|
|
805
|
+
...base(target.account, w.id, "unknown", "AMBIGUOUS"),
|
|
806
|
+
targetIndex: target.targetIndex,
|
|
807
|
+
reason: "ambiguous-submission",
|
|
808
|
+
diagnostic: error.message,
|
|
809
|
+
};
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
finally {
|
|
814
|
+
await store.release?.(w.id);
|
|
815
|
+
}
|
|
486
816
|
},
|
|
487
817
|
async get(ref, c) {
|
|
488
818
|
authorize(ref, "threads.posts.get");
|
|
489
819
|
return native.getPost(ref.postId, c);
|
|
490
820
|
},
|
|
491
|
-
async
|
|
821
|
+
async removeFromPlatform(ref, c) {
|
|
822
|
+
authorize(ref, "threads.posts.delete");
|
|
823
|
+
return native.deletePost({
|
|
824
|
+
account: accountRef(backend, ref.accountId),
|
|
825
|
+
postId: ref.postId,
|
|
826
|
+
context: c,
|
|
827
|
+
});
|
|
828
|
+
},
|
|
829
|
+
async getDelivery(ref, c) {
|
|
492
830
|
authorize(ref, "threads.posts.status");
|
|
493
831
|
const w = await store.get(ref.deliveryId);
|
|
494
832
|
if (!w || w.backend !== backend || w.accountId !== ref.accountId)
|
|
495
833
|
fail("threads.posts.status", "Workflow handle is not authorized for this account.", "unauthorized");
|
|
496
834
|
if (w.nativeId)
|
|
835
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
497
836
|
return {
|
|
498
837
|
...base(accountRef(backend, ref.accountId), w.id, "published", "PUBLISHED"),
|
|
499
838
|
post: platformPostRef({
|
|
@@ -504,25 +843,54 @@ export function threads(options) {
|
|
|
504
843
|
}),
|
|
505
844
|
};
|
|
506
845
|
if (w.stage === "unknown")
|
|
846
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
507
847
|
return {
|
|
508
848
|
...base(accountRef(backend, ref.accountId), w.id, "unknown", w.backendState),
|
|
509
849
|
reason: "ambiguous-submission",
|
|
510
850
|
diagnostic: "Explicit resumePublication is required.",
|
|
511
851
|
};
|
|
512
|
-
|
|
852
|
+
if (!(await store.claim(w.id)))
|
|
853
|
+
return base(accountRef(backend, ref.accountId), w.id, "processing", "CLAIMED");
|
|
854
|
+
try {
|
|
855
|
+
return await resume((await store.get(w.id)) ?? w, accountRef(backend, ref.accountId), c);
|
|
856
|
+
}
|
|
857
|
+
finally {
|
|
858
|
+
await store.release?.(w.id);
|
|
859
|
+
}
|
|
513
860
|
},
|
|
514
861
|
},
|
|
515
862
|
comments: {
|
|
516
863
|
async list(post, input, context) {
|
|
517
864
|
authorize(post, "threads.comments.list");
|
|
518
865
|
const result = await request(`${encodeURIComponent(post.postId)}/replies?fields=id,text,username,timestamp${input.cursor ? `&after=${encodeURIComponent(input.cursor)}` : ""}`, { method: "GET" }, "threads.comments.list", context);
|
|
866
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
519
867
|
const rows = Array.isArray(result["data"]) ? result["data"] : [];
|
|
520
868
|
return {
|
|
521
869
|
items: rows,
|
|
870
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
871
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
872
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- provider payload is validated at this adapter boundary.
|
|
873
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
874
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
875
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
876
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated external boundary or fixture contract.
|
|
877
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
522
878
|
...(typeof result["paging"] === "object" &&
|
|
523
879
|
result["paging"] &&
|
|
524
|
-
|
|
525
|
-
|
|
880
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
881
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
882
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- provider payload is validated at this adapter boundary.
|
|
883
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract.
|
|
884
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
885
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract.
|
|
886
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated external boundary or fixture contract.
|
|
887
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract.
|
|
888
|
+
typeof result["paging"]["cursors"] === "object" &&
|
|
889
|
+
result["paging"]["cursors"] !== null &&
|
|
890
|
+
typeof result["paging"]["cursors"]["after"] === "string"
|
|
891
|
+
? {
|
|
892
|
+
nextCursor: String(result["paging"]["cursors"]["after"]),
|
|
893
|
+
}
|
|
526
894
|
: {}),
|
|
527
895
|
};
|
|
528
896
|
},
|
|
@@ -530,25 +898,32 @@ export function threads(options) {
|
|
|
530
898
|
authorize(comment, "threads.comments.reply");
|
|
531
899
|
if (!content.text.trim())
|
|
532
900
|
fail("threads.comments.reply", "Comment text is required.", "invalid_input");
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
};
|
|
901
|
+
const container = await request(`${encodeURIComponent(comment.accountId)}/threads?media_type=TEXT&text=${encodeURIComponent(content.text)}&reply_to_id=${encodeURIComponent(comment.commentId)}`, { method: "POST" }, "threads.comments.reply", context);
|
|
902
|
+
const creationId = optionalString(container["id"]);
|
|
903
|
+
if (creationId === undefined)
|
|
904
|
+
fail("threads.comments.reply", "Threads did not return a reply container ID.");
|
|
905
|
+
const result = await request(`${encodeURIComponent(comment.accountId)}/threads_publish?creation_id=${encodeURIComponent(creationId)}`, { method: "POST" }, "threads.comments.reply", context);
|
|
906
|
+
return { ...comment, commentId: optionalString(result["id"]) ?? comment.commentId };
|
|
538
907
|
},
|
|
539
908
|
},
|
|
540
909
|
analytics: {
|
|
541
910
|
getAccountMetrics,
|
|
542
911
|
async getPostMetrics(post, c) {
|
|
543
912
|
authorize(post, "threads.analytics");
|
|
544
|
-
const r = await request(`${encodeURIComponent(post.postId)}/insights?metric=views,likes,replies,reposts,quotes,shares`,
|
|
913
|
+
const r = await request(`${encodeURIComponent(post.postId)}/insights?metric=views,likes,replies,reposts,quotes,shares,link_total_values`,
|
|
914
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
915
|
+
{ method: "GET", ...(c.signal ? { signal: c.signal } : {}) }, "threads.analytics", c);
|
|
545
916
|
return array(r["data"]).flatMap((x) => {
|
|
546
917
|
const row = object(x);
|
|
547
918
|
const first = Array.isArray(row["values"]) ? row["values"][0] : undefined;
|
|
548
|
-
const value =
|
|
919
|
+
const value =
|
|
920
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
921
|
+
first && typeof first === "object"
|
|
549
922
|
? optionalNumber(object(first)["value"])
|
|
550
923
|
: optionalNumber(row["value"]);
|
|
551
|
-
const
|
|
924
|
+
const rawName = row["name"];
|
|
925
|
+
const name = rawName === "link_total_values" ? "clicks" : rawName;
|
|
926
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
552
927
|
return value === undefined || typeof name !== "string"
|
|
553
928
|
? []
|
|
554
929
|
: [
|