@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,6 +1,7 @@
|
|
|
1
|
+
/* oxlint-disable anti-slop/no-chained-type-assertions, anti-slop/no-known-value-widening, anti-slop/require-safety-comment-for-type-assertion, anti-slop/require-readable-spacing, anti-slop/no-conditional-empty-object-spread, anti-slop/no-runtime-typeof -- validated external boundary or fixture contract. */
|
|
1
2
|
import { readBinary } from "../transport/binary.js";
|
|
2
3
|
import { remainingBudget } from "../transport/budget.js";
|
|
3
|
-
import { connectedAccountRef, defineAdapter, platformPostRef, } from "../core/index.js";
|
|
4
|
+
import { connectedAccountRef, defineAdapter, platformPostRef, profileRef, } from "../core/index.js";
|
|
4
5
|
import { SocialError } from "../core/errors.js";
|
|
5
6
|
import { abortable, createHttp, HttpError } from "../transport/http.js";
|
|
6
7
|
import { httpsUrl } from "../transport/upload.js";
|
|
@@ -35,6 +36,7 @@ function accountMatches(ref, auth, backend) {
|
|
|
35
36
|
function authHeaders(auth) {
|
|
36
37
|
return auth.accessJwt === undefined ? {} : { Authorization: `Bearer ${auth.accessJwt}` };
|
|
37
38
|
}
|
|
39
|
+
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- validated boundary or fixture contract.
|
|
38
40
|
function operationError(operation, error, mutation = true) {
|
|
39
41
|
if (error instanceof SocialError)
|
|
40
42
|
return error;
|
|
@@ -42,17 +44,35 @@ function operationError(operation, error, mutation = true) {
|
|
|
42
44
|
const ambiguous = mutation &&
|
|
43
45
|
error.dispatched &&
|
|
44
46
|
(error.kind !== "http" || (error.status !== undefined && error.status >= 500));
|
|
47
|
+
const code = ambiguous
|
|
48
|
+
? "ambiguous_outcome"
|
|
49
|
+
: error.kind === "timeout"
|
|
50
|
+
? "timeout"
|
|
51
|
+
: error.kind === "cancelled"
|
|
52
|
+
? "cancelled"
|
|
53
|
+
: error.status === 401
|
|
54
|
+
? "reconnect_required"
|
|
55
|
+
: error.status === 403
|
|
56
|
+
? "missing_permission"
|
|
57
|
+
: error.status === 404
|
|
58
|
+
? "not_found"
|
|
59
|
+
: error.status === 429
|
|
60
|
+
? "rate_limited"
|
|
61
|
+
: "upstream_failure";
|
|
45
62
|
return new SocialError({
|
|
46
|
-
code
|
|
47
|
-
? "ambiguous_outcome"
|
|
48
|
-
: error.kind === "timeout"
|
|
49
|
-
? "timeout"
|
|
50
|
-
: error.kind === "cancelled"
|
|
51
|
-
? "cancelled"
|
|
52
|
-
: "upstream_failure",
|
|
63
|
+
code,
|
|
53
64
|
operation,
|
|
54
65
|
message: error.message,
|
|
55
|
-
retryDisposition: ambiguous
|
|
66
|
+
retryDisposition: ambiguous
|
|
67
|
+
? { kind: "reconcile-first" }
|
|
68
|
+
: error.status === 401
|
|
69
|
+
? { kind: "after-reconnect" }
|
|
70
|
+
: error.status === 429 && error.retryAfterMs !== undefined
|
|
71
|
+
? { kind: "after-delay", delayMs: error.retryAfterMs }
|
|
72
|
+
: !mutation && error.status !== undefined && error.status >= 500
|
|
73
|
+
? { kind: "after-delay", delayMs: 1000 }
|
|
74
|
+
: { kind: "never" },
|
|
75
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
56
76
|
...(error.status === undefined ? {} : { upstreamStatus: error.status }),
|
|
57
77
|
});
|
|
58
78
|
}
|
|
@@ -64,6 +84,7 @@ function operationError(operation, error, mutation = true) {
|
|
|
64
84
|
cause: error,
|
|
65
85
|
});
|
|
66
86
|
}
|
|
87
|
+
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- validated boundary or fixture contract.
|
|
67
88
|
function postRef(value) {
|
|
68
89
|
const record = object(value);
|
|
69
90
|
return { uri: string(record["uri"]), cid: string(record["cid"]) };
|
|
@@ -72,7 +93,7 @@ function linkFacets(text) {
|
|
|
72
93
|
const facets = [];
|
|
73
94
|
const urlPattern = /https?:\/\/[^\s<>]+/g;
|
|
74
95
|
for (const match of text.matchAll(urlPattern)) {
|
|
75
|
-
const value = match[0];
|
|
96
|
+
const value = match[0]?.replace(/[.,;:!?)]*$/u, "");
|
|
76
97
|
const start = match.index;
|
|
77
98
|
if (start === undefined || value === undefined)
|
|
78
99
|
continue;
|
|
@@ -86,13 +107,16 @@ function linkFacets(text) {
|
|
|
86
107
|
}
|
|
87
108
|
return facets;
|
|
88
109
|
}
|
|
110
|
+
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- validated boundary or fixture contract.
|
|
89
111
|
function richTextOptions(text, options) {
|
|
90
112
|
const invalid = (message) => {
|
|
91
113
|
throw new SocialError({ code: "invalid_input", operation: "bluesky.prepare", message });
|
|
92
114
|
};
|
|
93
115
|
if (options !== undefined &&
|
|
116
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
94
117
|
(options === null || typeof options !== "object" || Array.isArray(options)))
|
|
95
118
|
invalid("Bluesky options must be an object.");
|
|
119
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
96
120
|
const config = (options ?? {});
|
|
97
121
|
if (Object.keys(config).some((key) => key !== "languages" && key !== "mentions"))
|
|
98
122
|
invalid("A supplied Bluesky option is not supported.");
|
|
@@ -101,9 +125,11 @@ function richTextOptions(text, options) {
|
|
|
101
125
|
const languages = config["languages"];
|
|
102
126
|
if (!Array.isArray(languages) ||
|
|
103
127
|
languages.length > 3 ||
|
|
128
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
104
129
|
languages.some((value) => typeof value !== "string"))
|
|
105
130
|
invalid("Bluesky accepts at most three language tags.");
|
|
106
131
|
try {
|
|
132
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
107
133
|
langs = Intl.getCanonicalLocales(languages);
|
|
108
134
|
}
|
|
109
135
|
catch {
|
|
@@ -116,11 +142,16 @@ function richTextOptions(text, options) {
|
|
|
116
142
|
if (!Array.isArray(mentions) || mentions.length > 100)
|
|
117
143
|
invalid("Bluesky mentions must be an array of at most 100 DID references.");
|
|
118
144
|
const spans = [];
|
|
145
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
119
146
|
for (const value of mentions) {
|
|
147
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
120
148
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
121
149
|
invalid("Each mention requires UTF-8 offsets and a DID.");
|
|
122
150
|
const start = value["byteStart"], end = value["byteEnd"], did = value["did"];
|
|
123
|
-
if (
|
|
151
|
+
if (
|
|
152
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
153
|
+
typeof start !== "number" ||
|
|
154
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
124
155
|
typeof end !== "number" ||
|
|
125
156
|
!Number.isSafeInteger(start) ||
|
|
126
157
|
!Number.isSafeInteger(end) ||
|
|
@@ -129,14 +160,19 @@ function richTextOptions(text, options) {
|
|
|
129
160
|
end > bytes.length ||
|
|
130
161
|
(bytes[start] & 0xc0) === 0x80 ||
|
|
131
162
|
(end < bytes.length && (bytes[end] & 0xc0) === 0x80) ||
|
|
163
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
132
164
|
typeof did !== "string" ||
|
|
133
165
|
!/^did:[a-z]+:[A-Za-z0-9._:%-]+$/.test(did))
|
|
134
166
|
invalid("Mention offsets must span complete UTF-8 characters and identify a DID.");
|
|
135
|
-
|
|
167
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
168
|
+
const byteStart = start,
|
|
169
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
170
|
+
byteEnd = end;
|
|
136
171
|
if (!new TextDecoder().decode(bytes.slice(byteStart, byteEnd)).startsWith("@"))
|
|
137
172
|
invalid("A mention span must start with @.");
|
|
138
173
|
if (spans.some((span) => byteStart < span.end && byteEnd > span.start) ||
|
|
139
174
|
facets.some((facet) => {
|
|
175
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
140
176
|
const index = facet["index"];
|
|
141
177
|
return byteStart < Number(index["byteEnd"]) && byteEnd > Number(index["byteStart"]);
|
|
142
178
|
}))
|
|
@@ -144,15 +180,35 @@ function richTextOptions(text, options) {
|
|
|
144
180
|
spans.push({ start: byteStart, end: byteEnd });
|
|
145
181
|
facets.push({
|
|
146
182
|
index: { byteStart, byteEnd },
|
|
183
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
147
184
|
features: [{ $type: "app.bsky.richtext.facet#mention", did: did }],
|
|
148
185
|
});
|
|
149
186
|
}
|
|
150
|
-
facets.sort((left, right) =>
|
|
187
|
+
facets.sort((left, right) =>
|
|
188
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
189
|
+
Number(left["index"]["byteStart"]) -
|
|
190
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
151
191
|
Number(right["index"]["byteStart"]));
|
|
192
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
152
193
|
return { ...(langs === undefined ? {} : { langs }), ...(facets.length ? { facets } : {}) };
|
|
153
194
|
}
|
|
154
195
|
function graphemeCount(text) {
|
|
155
|
-
const Segmenter =
|
|
196
|
+
const Segmenter =
|
|
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 -- provider payload is validated at this adapter boundary.
|
|
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
|
+
// oxlint-disable-next-line anti-slop/no-chained-type-assertions -- validated external boundary or fixture contract.
|
|
206
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract.
|
|
207
|
+
// oxlint-disable-next-line anti-slop/no-chained-type-assertions -- validated external boundary or fixture contract.
|
|
208
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract.
|
|
209
|
+
// oxlint-disable-next-line anti-slop/no-chained-type-assertions -- validated external boundary or fixture contract.
|
|
210
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated external boundary or fixture contract.
|
|
211
|
+
Intl.Segmenter;
|
|
156
212
|
return Segmenter === undefined
|
|
157
213
|
? Array.from(text).length
|
|
158
214
|
: [...new Segmenter(undefined, { granularity: "grapheme" }).segment(text)].length;
|
|
@@ -247,6 +303,7 @@ export function bluesky(options) {
|
|
|
247
303
|
return options.session.fetchHandler(url.pathname + url.search, init);
|
|
248
304
|
}
|
|
249
305
|
: fetcher,
|
|
306
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
250
307
|
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
251
308
|
});
|
|
252
309
|
const auth = options.auth;
|
|
@@ -298,6 +355,26 @@ export function bluesky(options) {
|
|
|
298
355
|
availability: "available",
|
|
299
356
|
formats: ["text", "image"],
|
|
300
357
|
},
|
|
358
|
+
...[
|
|
359
|
+
"graph.read",
|
|
360
|
+
"graph.follow",
|
|
361
|
+
"graph.unfollow",
|
|
362
|
+
"graph.block",
|
|
363
|
+
"graph.unblock",
|
|
364
|
+
"graph.mute",
|
|
365
|
+
"graph.unmute",
|
|
366
|
+
"likes.read",
|
|
367
|
+
"likes.write",
|
|
368
|
+
"lists.read",
|
|
369
|
+
"lists.write",
|
|
370
|
+
"profiles.search",
|
|
371
|
+
"moderation.report",
|
|
372
|
+
].map((operation) => ({
|
|
373
|
+
operation,
|
|
374
|
+
platform: "bluesky",
|
|
375
|
+
availability: "available",
|
|
376
|
+
requiredScopes: ["repo"],
|
|
377
|
+
})),
|
|
301
378
|
{
|
|
302
379
|
operation: "posts.list",
|
|
303
380
|
platform: "bluesky",
|
|
@@ -327,7 +404,7 @@ export function bluesky(options) {
|
|
|
327
404
|
{
|
|
328
405
|
operation: "posts.publish.video",
|
|
329
406
|
platform: "bluesky",
|
|
330
|
-
availability: "
|
|
407
|
+
availability: "not-implemented-by-adapter",
|
|
331
408
|
formats: ["video"],
|
|
332
409
|
requiredScopes: ["repo"],
|
|
333
410
|
},
|
|
@@ -335,14 +412,14 @@ export function bluesky(options) {
|
|
|
335
412
|
"posts.repost",
|
|
336
413
|
"posts.quote",
|
|
337
414
|
"posts.delete",
|
|
338
|
-
"
|
|
339
|
-
"graph.block",
|
|
340
|
-
"graph.mute",
|
|
415
|
+
"posts.removeFromPlatform",
|
|
341
416
|
"notifications.read",
|
|
342
417
|
"notifications.seen",
|
|
343
418
|
"profile.read",
|
|
419
|
+
"profiles.read",
|
|
344
420
|
"profile.update",
|
|
345
421
|
"feeds.read",
|
|
422
|
+
"search.posts",
|
|
346
423
|
"chat.read",
|
|
347
424
|
"chat.write",
|
|
348
425
|
].map((operation) => ({
|
|
@@ -356,16 +433,21 @@ export function bluesky(options) {
|
|
|
356
433
|
};
|
|
357
434
|
async function xrpc(method, context, init = {}) {
|
|
358
435
|
try {
|
|
436
|
+
const headers = new Headers(init.headers);
|
|
437
|
+
if (typeof init.body === "string" && !headers.has("Content-Type"))
|
|
438
|
+
headers.set("Content-Type", "application/json");
|
|
439
|
+
if (method.startsWith("chat.bsky.") && !headers.has("atproto-proxy"))
|
|
440
|
+
headers.set("atproto-proxy", "did:web:api.bsky.chat#bsky_chat");
|
|
359
441
|
return await http({
|
|
360
442
|
url: endpoint(service, method),
|
|
361
443
|
timeoutMs: remainingBudget(context),
|
|
362
444
|
method: init.body === undefined ? "GET" : "POST",
|
|
363
445
|
...(options.session
|
|
364
|
-
?
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
: { headers: { ...authHeaders(auth), ...init.headers } }),
|
|
446
|
+
? { headers }
|
|
447
|
+
: { headers: { ...authHeaders(auth), ...Object.fromEntries(headers.entries()) } }),
|
|
448
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
368
449
|
...(init.body === undefined ? {} : { body: init.body }),
|
|
450
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
369
451
|
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
370
452
|
maxAttempts: init.maxAttempts ??
|
|
371
453
|
(init.body === undefined ? Math.min(context.retryBudget.maxAttempts, 3) : 1),
|
|
@@ -387,7 +469,9 @@ export function bluesky(options) {
|
|
|
387
469
|
message: "The configured credential belongs to a different DID.",
|
|
388
470
|
});
|
|
389
471
|
}
|
|
472
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
390
473
|
const handle = typeof session["handle"] === "string" ? session["handle"] : undefined;
|
|
474
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
391
475
|
return { did, ...(handle === undefined ? {} : { handle }) };
|
|
392
476
|
}
|
|
393
477
|
const nativeContext = (context, operation) => context ?? {
|
|
@@ -403,6 +487,34 @@ export function bluesky(options) {
|
|
|
403
487
|
message: "Account reference does not belong to this Bluesky adapter.",
|
|
404
488
|
});
|
|
405
489
|
};
|
|
490
|
+
const pageQuery = (input) => {
|
|
491
|
+
const limit = input.limit ?? 50;
|
|
492
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
493
|
+
throw new SocialError({
|
|
494
|
+
code: "invalid_input",
|
|
495
|
+
operation: "bluesky.graph.read",
|
|
496
|
+
message: "Page size must be between 1 and 100.",
|
|
497
|
+
});
|
|
498
|
+
return new URLSearchParams({
|
|
499
|
+
limit: String(limit),
|
|
500
|
+
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
501
|
+
});
|
|
502
|
+
};
|
|
503
|
+
const recordKey = (uri, collection) => {
|
|
504
|
+
const prefix = `at://${auth.did}/${collection}/`;
|
|
505
|
+
const key = uri.startsWith(prefix) ? uri.slice(prefix.length) : "";
|
|
506
|
+
if (!/^[A-Za-z0-9._~:-]{1,512}$/.test(key) || key === "." || key === "..")
|
|
507
|
+
throw new SocialError({
|
|
508
|
+
code: "invalid_input",
|
|
509
|
+
operation: "bluesky.record.delete",
|
|
510
|
+
message: "Record URI must belong to this account.",
|
|
511
|
+
});
|
|
512
|
+
return key;
|
|
513
|
+
};
|
|
514
|
+
const actorPage = (response, key) => ({
|
|
515
|
+
actors: array(response[key] ?? []).map((value) => object(value)),
|
|
516
|
+
...(typeof response["cursor"] === "string" ? { cursor: response["cursor"] } : {}),
|
|
517
|
+
});
|
|
406
518
|
const native = {
|
|
407
519
|
async getPost(input) {
|
|
408
520
|
const context = input.context ?? {
|
|
@@ -420,6 +532,7 @@ export function bluesky(options) {
|
|
|
420
532
|
operation: "bluesky.getPost",
|
|
421
533
|
message: "Bluesky post was not found.",
|
|
422
534
|
});
|
|
535
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
423
536
|
return object(post);
|
|
424
537
|
},
|
|
425
538
|
async getPostThread(input) {
|
|
@@ -429,8 +542,87 @@ export function bluesky(options) {
|
|
|
429
542
|
backendInstance: backend,
|
|
430
543
|
};
|
|
431
544
|
const query = new URLSearchParams({ uri: input.uri });
|
|
545
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
432
546
|
return object(await xrpc(`app.bsky.feed.getPostThread?${query.toString()}`, context));
|
|
433
547
|
},
|
|
548
|
+
async searchPosts(input) {
|
|
549
|
+
const context = nativeContext(input.context, "bluesky.search.posts");
|
|
550
|
+
assertNativeAccount(input.account, context, "bluesky.search.posts");
|
|
551
|
+
const queryText = input.query.trim();
|
|
552
|
+
if (!queryText)
|
|
553
|
+
throw new SocialError({
|
|
554
|
+
code: "invalid_input",
|
|
555
|
+
operation: "bluesky.search.posts",
|
|
556
|
+
message: "Bluesky search query is required.",
|
|
557
|
+
retryDisposition: { kind: "never" },
|
|
558
|
+
});
|
|
559
|
+
const limit = input.limit ?? 50;
|
|
560
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
561
|
+
throw new SocialError({
|
|
562
|
+
code: "invalid_input",
|
|
563
|
+
operation: "bluesky.search.posts",
|
|
564
|
+
message: "Bluesky search page size must be between 1 and 100.",
|
|
565
|
+
retryDisposition: { kind: "never" },
|
|
566
|
+
});
|
|
567
|
+
if (input.sort !== undefined && input.sort !== "latest" && input.sort !== "top")
|
|
568
|
+
throw new SocialError({
|
|
569
|
+
code: "invalid_input",
|
|
570
|
+
operation: "bluesky.search.posts",
|
|
571
|
+
message: "Bluesky search sort must be latest or top.",
|
|
572
|
+
retryDisposition: { kind: "never" },
|
|
573
|
+
});
|
|
574
|
+
if (input.scope === "all")
|
|
575
|
+
throw new SocialError({
|
|
576
|
+
code: "invalid_input",
|
|
577
|
+
operation: "bluesky.search.posts",
|
|
578
|
+
message: "Bluesky search does not support scope 'all'.",
|
|
579
|
+
});
|
|
580
|
+
if (input.context !== undefined && input.context.backendInstance !== backend)
|
|
581
|
+
throw new SocialError({
|
|
582
|
+
code: "unauthorized",
|
|
583
|
+
operation: "bluesky.search.posts",
|
|
584
|
+
message: "Account reference does not belong to this Bluesky adapter.",
|
|
585
|
+
});
|
|
586
|
+
const query = new URLSearchParams({ q: queryText, limit: String(limit) });
|
|
587
|
+
const optional = {
|
|
588
|
+
cursor: input.cursor,
|
|
589
|
+
sort: input.sort,
|
|
590
|
+
since: input.since,
|
|
591
|
+
until: input.until,
|
|
592
|
+
mentions: input.mentions,
|
|
593
|
+
author: input.author,
|
|
594
|
+
lang: input.lang,
|
|
595
|
+
domain: input.domain,
|
|
596
|
+
url: input.url,
|
|
597
|
+
};
|
|
598
|
+
for (const [key, value] of Object.entries(optional))
|
|
599
|
+
if (value !== undefined && value.trim() !== "")
|
|
600
|
+
query.set(key, value);
|
|
601
|
+
for (const tag of input.tags ?? []) {
|
|
602
|
+
const normalizedTag = tag.trim();
|
|
603
|
+
if (normalizedTag)
|
|
604
|
+
query.append("tag", normalizedTag);
|
|
605
|
+
}
|
|
606
|
+
const response = object(await xrpc(`app.bsky.feed.searchPosts?${query}`, context));
|
|
607
|
+
const posts = array(response["posts"]).map((post) => {
|
|
608
|
+
const value = object(post);
|
|
609
|
+
// The XRPC transport has already decoded a JSON payload; `object` validates
|
|
610
|
+
// the record boundary while the recursive JSON shape is preserved for callers.
|
|
611
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion
|
|
612
|
+
return value;
|
|
613
|
+
});
|
|
614
|
+
const cursorValue = response["cursor"];
|
|
615
|
+
const hitsTotalValue = response["hitsTotal"];
|
|
616
|
+
return {
|
|
617
|
+
posts,
|
|
618
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
619
|
+
...(typeof cursorValue === "string" ? { cursor: cursorValue } : {}),
|
|
620
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
621
|
+
...(typeof hitsTotalValue === "number" && Number.isSafeInteger(hitsTotalValue)
|
|
622
|
+
? { hitsTotal: hitsTotalValue }
|
|
623
|
+
: {}),
|
|
624
|
+
};
|
|
625
|
+
},
|
|
434
626
|
async likePost(input) {
|
|
435
627
|
if (!accountMatches(input.account, auth, backend) ||
|
|
436
628
|
(input.context && input.context.backendInstance !== backend))
|
|
@@ -464,9 +656,12 @@ export function bluesky(options) {
|
|
|
464
656
|
}));
|
|
465
657
|
const prefix = `at://${auth.did}/app.bsky.feed.like/`;
|
|
466
658
|
const uri = response["uri"], cid = response["cid"];
|
|
467
|
-
if (
|
|
659
|
+
if (
|
|
660
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
661
|
+
typeof uri !== "string" ||
|
|
468
662
|
!uri.startsWith(prefix) ||
|
|
469
663
|
!/^[A-Za-z0-9._~:-]{1,512}$/.test(uri.slice(prefix.length)) ||
|
|
664
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
470
665
|
typeof cid !== "string" ||
|
|
471
666
|
!cid)
|
|
472
667
|
throw new SocialError({
|
|
@@ -521,7 +716,7 @@ export function bluesky(options) {
|
|
|
521
716
|
async quotePost(input) {
|
|
522
717
|
const context = nativeContext(input.context, "bluesky.quote");
|
|
523
718
|
assertNativeAccount(input.account, context, "bluesky.quote");
|
|
524
|
-
if (!input.text.trim() || input.text
|
|
719
|
+
if (!input.text.trim() || graphemeCount(input.text) > 300)
|
|
525
720
|
throw new SocialError({
|
|
526
721
|
code: "invalid_input",
|
|
527
722
|
operation: "bluesky.quote",
|
|
@@ -556,23 +751,17 @@ export function bluesky(options) {
|
|
|
556
751
|
body: JSON.stringify({ repo: auth.did, collection: "app.bsky.feed.post", rkey }),
|
|
557
752
|
});
|
|
558
753
|
},
|
|
559
|
-
async uploadVideo(
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
operation: "bluesky.video.upload",
|
|
566
|
-
message: "Provide a non-empty video Blob.",
|
|
567
|
-
});
|
|
568
|
-
return object(await xrpc("app.bsky.video.uploadVideo", context, {
|
|
569
|
-
body: input.video,
|
|
570
|
-
headers: { "Content-Type": input.mimeType ?? input.video.type },
|
|
571
|
-
}));
|
|
754
|
+
async uploadVideo(_input) {
|
|
755
|
+
throw new SocialError({
|
|
756
|
+
code: "unsupported_capability",
|
|
757
|
+
operation: "bluesky.video.upload",
|
|
758
|
+
message: "Bluesky video upload requires the video service authentication and job polling flow; this adapter does not implement it.",
|
|
759
|
+
});
|
|
572
760
|
},
|
|
573
761
|
async follow(input) {
|
|
574
762
|
const context = nativeContext(input.context, "bluesky.follow");
|
|
575
763
|
assertNativeAccount(input.account, context, "bluesky.follow");
|
|
764
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
576
765
|
return object(await xrpc("com.atproto.repo.createRecord", context, {
|
|
577
766
|
body: JSON.stringify({
|
|
578
767
|
repo: auth.did,
|
|
@@ -585,9 +774,21 @@ export function bluesky(options) {
|
|
|
585
774
|
}),
|
|
586
775
|
}));
|
|
587
776
|
},
|
|
777
|
+
async unfollow(input) {
|
|
778
|
+
const context = nativeContext(input.context, "bluesky.unfollow");
|
|
779
|
+
assertNativeAccount(input.account, context, "bluesky.unfollow");
|
|
780
|
+
await xrpc("com.atproto.repo.deleteRecord", context, {
|
|
781
|
+
body: JSON.stringify({
|
|
782
|
+
repo: auth.did,
|
|
783
|
+
collection: "app.bsky.graph.follow",
|
|
784
|
+
rkey: recordKey(input.followUri, "app.bsky.graph.follow"),
|
|
785
|
+
}),
|
|
786
|
+
});
|
|
787
|
+
},
|
|
588
788
|
async block(input) {
|
|
589
789
|
const context = nativeContext(input.context, "bluesky.block");
|
|
590
790
|
assertNativeAccount(input.account, context, "bluesky.block");
|
|
791
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
591
792
|
return object(await xrpc("com.atproto.repo.createRecord", context, {
|
|
592
793
|
body: JSON.stringify({
|
|
593
794
|
repo: auth.did,
|
|
@@ -600,20 +801,284 @@ export function bluesky(options) {
|
|
|
600
801
|
}),
|
|
601
802
|
}));
|
|
602
803
|
},
|
|
804
|
+
async unblock(input) {
|
|
805
|
+
const context = nativeContext(input.context, "bluesky.unblock");
|
|
806
|
+
assertNativeAccount(input.account, context, "bluesky.unblock");
|
|
807
|
+
await xrpc("com.atproto.repo.deleteRecord", context, {
|
|
808
|
+
body: JSON.stringify({
|
|
809
|
+
repo: auth.did,
|
|
810
|
+
collection: "app.bsky.graph.block",
|
|
811
|
+
rkey: recordKey(input.blockUri, "app.bsky.graph.block"),
|
|
812
|
+
}),
|
|
813
|
+
});
|
|
814
|
+
},
|
|
603
815
|
async mute(input) {
|
|
604
816
|
const context = nativeContext(input.context, "bluesky.mute");
|
|
605
817
|
assertNativeAccount(input.account, context, "bluesky.mute");
|
|
606
|
-
|
|
818
|
+
await xrpc("app.bsky.graph.muteActor", context, {
|
|
607
819
|
body: JSON.stringify({ actor: input.did }),
|
|
608
|
-
})
|
|
820
|
+
});
|
|
821
|
+
},
|
|
822
|
+
async unmute(input) {
|
|
823
|
+
const context = nativeContext(input.context, "bluesky.unmute");
|
|
824
|
+
assertNativeAccount(input.account, context, "bluesky.unmute");
|
|
825
|
+
await xrpc("app.bsky.graph.unmuteActor", context, {
|
|
826
|
+
body: JSON.stringify({ actor: input.did }),
|
|
827
|
+
});
|
|
828
|
+
},
|
|
829
|
+
async getFollowers(input) {
|
|
830
|
+
const context = nativeContext(input.context, "bluesky.graph.followers");
|
|
831
|
+
assertNativeAccount(input.account, context, "bluesky.graph.followers");
|
|
832
|
+
const q = pageQuery(input);
|
|
833
|
+
q.set("actor", input.actor ?? auth.did);
|
|
834
|
+
return actorPage(object(await xrpc(`app.bsky.graph.getFollowers?${q}`, context)), "followers");
|
|
835
|
+
},
|
|
836
|
+
async getFollows(input) {
|
|
837
|
+
const context = nativeContext(input.context, "bluesky.graph.follows");
|
|
838
|
+
assertNativeAccount(input.account, context, "bluesky.graph.follows");
|
|
839
|
+
const q = pageQuery(input);
|
|
840
|
+
q.set("actor", input.actor ?? auth.did);
|
|
841
|
+
return actorPage(object(await xrpc(`app.bsky.graph.getFollows?${q}`, context)), "follows");
|
|
842
|
+
},
|
|
843
|
+
async getMutes(input) {
|
|
844
|
+
const context = nativeContext(input.context, "bluesky.graph.mutes");
|
|
845
|
+
assertNativeAccount(input.account, context, "bluesky.graph.mutes");
|
|
846
|
+
return actorPage(object(await xrpc(`app.bsky.graph.getMutes?${pageQuery(input)}`, context)), "mutes");
|
|
847
|
+
},
|
|
848
|
+
async getBlocks(input) {
|
|
849
|
+
const context = nativeContext(input.context, "bluesky.graph.blocks");
|
|
850
|
+
assertNativeAccount(input.account, context, "bluesky.graph.blocks");
|
|
851
|
+
return actorPage(object(await xrpc(`app.bsky.graph.getBlocks?${pageQuery(input)}`, context)), "blocks");
|
|
852
|
+
},
|
|
853
|
+
async getLikes(input) {
|
|
854
|
+
const context = nativeContext(input.context, "bluesky.likes.read");
|
|
855
|
+
assertNativeAccount(input.account, context, "bluesky.likes.read");
|
|
856
|
+
const q = pageQuery(input);
|
|
857
|
+
q.set("uri", input.uri);
|
|
858
|
+
if (input.cid)
|
|
859
|
+
q.set("cid", input.cid);
|
|
860
|
+
const value = object(await xrpc(`app.bsky.feed.getLikes?${q}`, context));
|
|
861
|
+
return {
|
|
862
|
+
likes: array(value["likes"] ?? []).map((entry) => object(entry)),
|
|
863
|
+
...(typeof value["cursor"] === "string" ? { cursor: value["cursor"] } : {}),
|
|
864
|
+
};
|
|
865
|
+
},
|
|
866
|
+
async getActorLikes(input) {
|
|
867
|
+
const context = nativeContext(input.context, "bluesky.likes.actor");
|
|
868
|
+
assertNativeAccount(input.account, context, "bluesky.likes.actor");
|
|
869
|
+
const q = pageQuery(input);
|
|
870
|
+
q.set("actor", input.actor ?? auth.did);
|
|
871
|
+
const value = object(await xrpc(`app.bsky.feed.getActorLikes?${q}`, context));
|
|
872
|
+
return {
|
|
873
|
+
feed: array(value["feed"] ?? []).map((entry) => object(entry)),
|
|
874
|
+
...(typeof value["cursor"] === "string" ? { cursor: value["cursor"] } : {}),
|
|
875
|
+
};
|
|
876
|
+
},
|
|
877
|
+
async searchActors(input) {
|
|
878
|
+
const context = nativeContext(input.context, "bluesky.profiles.search");
|
|
879
|
+
assertNativeAccount(input.account, context, "bluesky.profiles.search");
|
|
880
|
+
if (!input.query.trim())
|
|
881
|
+
throw new SocialError({
|
|
882
|
+
code: "invalid_input",
|
|
883
|
+
operation: "bluesky.profiles.search",
|
|
884
|
+
message: "Actor search query is required.",
|
|
885
|
+
});
|
|
886
|
+
const q = pageQuery(input);
|
|
887
|
+
q.set("q", input.query.trim());
|
|
888
|
+
return actorPage(object(await xrpc(`app.bsky.actor.searchActors?${q}`, context)), "actors");
|
|
889
|
+
},
|
|
890
|
+
async searchActorsTypeahead(input) {
|
|
891
|
+
const context = nativeContext(input.context, "bluesky.profiles.search");
|
|
892
|
+
assertNativeAccount(input.account, context, "bluesky.profiles.search");
|
|
893
|
+
if (!input.query.trim())
|
|
894
|
+
throw new SocialError({
|
|
895
|
+
code: "invalid_input",
|
|
896
|
+
operation: "bluesky.profiles.search",
|
|
897
|
+
message: "Actor search query is required.",
|
|
898
|
+
});
|
|
899
|
+
const limit = input.limit ?? 8;
|
|
900
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
901
|
+
throw new SocialError({
|
|
902
|
+
code: "invalid_input",
|
|
903
|
+
operation: "bluesky.profiles.search",
|
|
904
|
+
message: "Page size must be between 1 and 100.",
|
|
905
|
+
});
|
|
906
|
+
const q = new URLSearchParams({ q: input.query.trim(), limit: String(limit) });
|
|
907
|
+
return actorPage(object(await xrpc(`app.bsky.actor.searchActorsTypeahead?${q}`, context)), "actors");
|
|
908
|
+
},
|
|
909
|
+
async createList(input) {
|
|
910
|
+
const context = nativeContext(input.context, "bluesky.lists.create");
|
|
911
|
+
assertNativeAccount(input.account, context, "bluesky.lists.create");
|
|
912
|
+
const record = {
|
|
913
|
+
$type: "app.bsky.graph.list",
|
|
914
|
+
name: input.name,
|
|
915
|
+
purpose: input.purpose,
|
|
916
|
+
createdAt: new Date().toISOString(),
|
|
917
|
+
...(input.description === undefined ? {} : { description: input.description }),
|
|
918
|
+
};
|
|
919
|
+
return postRef(object(await xrpc("com.atproto.repo.createRecord", context, {
|
|
920
|
+
body: JSON.stringify({ repo: auth.did, collection: "app.bsky.graph.list", record }),
|
|
921
|
+
})));
|
|
922
|
+
},
|
|
923
|
+
async updateList(input) {
|
|
924
|
+
const context = nativeContext(input.context, "bluesky.lists.update");
|
|
925
|
+
assertNativeAccount(input.account, context, "bluesky.lists.update");
|
|
926
|
+
const existing = object(await xrpc(`com.atproto.repo.getRecord?repo=${encodeURIComponent(auth.did)}&collection=app.bsky.graph.list&rkey=${encodeURIComponent(recordKey(input.listUri, "app.bsky.graph.list"))}`, context));
|
|
927
|
+
const record = object(existing["value"]);
|
|
928
|
+
const next = {
|
|
929
|
+
...record,
|
|
930
|
+
name: input.name,
|
|
931
|
+
purpose: input.purpose,
|
|
932
|
+
...(input.description === undefined ? {} : { description: input.description }),
|
|
933
|
+
};
|
|
934
|
+
await xrpc("com.atproto.repo.putRecord", context, {
|
|
935
|
+
body: JSON.stringify({
|
|
936
|
+
repo: auth.did,
|
|
937
|
+
collection: "app.bsky.graph.list",
|
|
938
|
+
rkey: recordKey(input.listUri, "app.bsky.graph.list"),
|
|
939
|
+
record: next,
|
|
940
|
+
}),
|
|
941
|
+
});
|
|
942
|
+
},
|
|
943
|
+
async deleteList(input) {
|
|
944
|
+
const context = nativeContext(input.context, "bluesky.lists.delete");
|
|
945
|
+
assertNativeAccount(input.account, context, "bluesky.lists.delete");
|
|
946
|
+
await xrpc("com.atproto.repo.deleteRecord", context, {
|
|
947
|
+
body: JSON.stringify({
|
|
948
|
+
repo: auth.did,
|
|
949
|
+
collection: "app.bsky.graph.list",
|
|
950
|
+
rkey: recordKey(input.uri, "app.bsky.graph.list"),
|
|
951
|
+
}),
|
|
952
|
+
});
|
|
953
|
+
},
|
|
954
|
+
async addListItem(input) {
|
|
955
|
+
const context = nativeContext(input.context, "bluesky.lists.items.add");
|
|
956
|
+
assertNativeAccount(input.account, context, "bluesky.lists.items.add");
|
|
957
|
+
return postRef(object(await xrpc("com.atproto.repo.createRecord", context, {
|
|
958
|
+
body: JSON.stringify({
|
|
959
|
+
repo: auth.did,
|
|
960
|
+
collection: "app.bsky.graph.listitem",
|
|
961
|
+
record: {
|
|
962
|
+
$type: "app.bsky.graph.listitem",
|
|
963
|
+
list: input.listUri,
|
|
964
|
+
subject: input.subject,
|
|
965
|
+
createdAt: new Date().toISOString(),
|
|
966
|
+
},
|
|
967
|
+
}),
|
|
968
|
+
})));
|
|
969
|
+
},
|
|
970
|
+
async removeListItem(input) {
|
|
971
|
+
const context = nativeContext(input.context, "bluesky.lists.items.remove");
|
|
972
|
+
assertNativeAccount(input.account, context, "bluesky.lists.items.remove");
|
|
973
|
+
await xrpc("com.atproto.repo.deleteRecord", context, {
|
|
974
|
+
body: JSON.stringify({
|
|
975
|
+
repo: auth.did,
|
|
976
|
+
collection: "app.bsky.graph.listitem",
|
|
977
|
+
rkey: recordKey(input.uri, "app.bsky.graph.listitem"),
|
|
978
|
+
}),
|
|
979
|
+
});
|
|
980
|
+
},
|
|
981
|
+
async getList(input) {
|
|
982
|
+
const context = nativeContext(input.context, "bluesky.lists.get");
|
|
983
|
+
assertNativeAccount(input.account, context, "bluesky.lists.get");
|
|
984
|
+
const q = pageQuery(input);
|
|
985
|
+
q.set("list", input.listUri);
|
|
986
|
+
return object(await xrpc(`app.bsky.graph.getList?${q}`, context));
|
|
987
|
+
},
|
|
988
|
+
async getLists(input) {
|
|
989
|
+
const context = nativeContext(input.context, "bluesky.lists.list");
|
|
990
|
+
assertNativeAccount(input.account, context, "bluesky.lists.list");
|
|
991
|
+
const q = pageQuery(input);
|
|
992
|
+
q.set("actor", input.actor ?? auth.did);
|
|
993
|
+
return object(await xrpc(`app.bsky.graph.getLists?${q}`, context));
|
|
994
|
+
},
|
|
995
|
+
async muteList(input) {
|
|
996
|
+
const context = nativeContext(input.context, "bluesky.lists.mute");
|
|
997
|
+
assertNativeAccount(input.account, context, "bluesky.lists.mute");
|
|
998
|
+
await xrpc("app.bsky.graph.muteActorList", context, {
|
|
999
|
+
body: JSON.stringify({ list: input.listUri }),
|
|
1000
|
+
});
|
|
1001
|
+
},
|
|
1002
|
+
async unmuteList(input) {
|
|
1003
|
+
const context = nativeContext(input.context, "bluesky.lists.unmute");
|
|
1004
|
+
assertNativeAccount(input.account, context, "bluesky.lists.unmute");
|
|
1005
|
+
await xrpc("app.bsky.graph.unmuteActorList", context, {
|
|
1006
|
+
body: JSON.stringify({ list: input.listUri }),
|
|
1007
|
+
});
|
|
1008
|
+
},
|
|
1009
|
+
async blockList(input) {
|
|
1010
|
+
const context = nativeContext(input.context, "bluesky.lists.block");
|
|
1011
|
+
assertNativeAccount(input.account, context, "bluesky.lists.block");
|
|
1012
|
+
await xrpc("com.atproto.repo.createRecord", context, {
|
|
1013
|
+
body: JSON.stringify({
|
|
1014
|
+
repo: auth.did,
|
|
1015
|
+
collection: "app.bsky.graph.listblock",
|
|
1016
|
+
record: {
|
|
1017
|
+
$type: "app.bsky.graph.listblock",
|
|
1018
|
+
subject: input.listUri,
|
|
1019
|
+
createdAt: new Date().toISOString(),
|
|
1020
|
+
},
|
|
1021
|
+
}),
|
|
1022
|
+
});
|
|
1023
|
+
},
|
|
1024
|
+
async unblockList(input) {
|
|
1025
|
+
const context = nativeContext(input.context, "bluesky.lists.unblock");
|
|
1026
|
+
assertNativeAccount(input.account, context, "bluesky.lists.unblock");
|
|
1027
|
+
let cursor;
|
|
1028
|
+
let record;
|
|
1029
|
+
do {
|
|
1030
|
+
const query = new URLSearchParams({
|
|
1031
|
+
repo: auth.did,
|
|
1032
|
+
collection: "app.bsky.graph.listblock",
|
|
1033
|
+
limit: "100",
|
|
1034
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
1035
|
+
});
|
|
1036
|
+
const records = object(await xrpc(`com.atproto.repo.listRecords?${query.toString()}`, context));
|
|
1037
|
+
record = array(records["records"])
|
|
1038
|
+
.map((value) => object(value))
|
|
1039
|
+
.find((value) => object(value["value"])["subject"] === input.listUri);
|
|
1040
|
+
cursor = typeof records["cursor"] === "string" ? records["cursor"] : undefined;
|
|
1041
|
+
} while (record === undefined && cursor !== undefined);
|
|
1042
|
+
if (record === undefined)
|
|
1043
|
+
throw new SocialError({
|
|
1044
|
+
code: "invalid_input",
|
|
1045
|
+
operation: "bluesky.lists.unblock",
|
|
1046
|
+
message: "No list block record was found for this list.",
|
|
1047
|
+
});
|
|
1048
|
+
await xrpc("com.atproto.repo.deleteRecord", context, {
|
|
1049
|
+
body: JSON.stringify({
|
|
1050
|
+
repo: auth.did,
|
|
1051
|
+
collection: "app.bsky.graph.listblock",
|
|
1052
|
+
rkey: recordKey(string(record["uri"]), "app.bsky.graph.listblock"),
|
|
1053
|
+
}),
|
|
1054
|
+
});
|
|
1055
|
+
},
|
|
1056
|
+
async createModerationReport(input) {
|
|
1057
|
+
const context = nativeContext(input.context, "bluesky.moderation.report");
|
|
1058
|
+
assertNativeAccount(input.account, context, "bluesky.moderation.report");
|
|
1059
|
+
const body = {
|
|
1060
|
+
reasonType: input.reasonType,
|
|
1061
|
+
subject: input.subject,
|
|
1062
|
+
...(input.reason === undefined ? {} : { reason: input.reason }),
|
|
1063
|
+
};
|
|
1064
|
+
return object(await xrpc("com.atproto.moderation.createReport", context, { body: JSON.stringify(body) }));
|
|
609
1065
|
},
|
|
610
1066
|
async listNotifications(input) {
|
|
611
1067
|
const context = nativeContext(input.context, "bluesky.notifications.list");
|
|
612
1068
|
assertNativeAccount(input.account, context, "bluesky.notifications.list");
|
|
1069
|
+
const limit = input.limit ?? 50;
|
|
1070
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
1071
|
+
throw new SocialError({
|
|
1072
|
+
code: "invalid_input",
|
|
1073
|
+
operation: "bluesky.notifications.list",
|
|
1074
|
+
message: "Page size must be between 1 and 100.",
|
|
1075
|
+
});
|
|
613
1076
|
const query = new URLSearchParams({
|
|
614
|
-
limit: String(
|
|
1077
|
+
limit: String(limit),
|
|
1078
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
615
1079
|
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
616
1080
|
});
|
|
1081
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
617
1082
|
return object(await xrpc(`app.bsky.notification.listNotifications?${query}`, context));
|
|
618
1083
|
},
|
|
619
1084
|
async markNotificationsSeen(input) {
|
|
@@ -626,6 +1091,7 @@ export function bluesky(options) {
|
|
|
626
1091
|
async getProfile(input) {
|
|
627
1092
|
const context = nativeContext(input.context, "bluesky.profile.get");
|
|
628
1093
|
assertNativeAccount(input.account, context, "bluesky.profile.get");
|
|
1094
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
629
1095
|
return object(await xrpc(`app.bsky.actor.getProfile?actor=${encodeURIComponent(input.actor ?? auth.did)}`, context));
|
|
630
1096
|
},
|
|
631
1097
|
async updateProfile(input) {
|
|
@@ -643,28 +1109,55 @@ export function bluesky(options) {
|
|
|
643
1109
|
async listFeeds(input) {
|
|
644
1110
|
const context = nativeContext(input.context, "bluesky.feeds.list");
|
|
645
1111
|
assertNativeAccount(input.account, context, "bluesky.feeds.list");
|
|
1112
|
+
const limit = input.limit ?? 50;
|
|
1113
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
1114
|
+
throw new SocialError({
|
|
1115
|
+
code: "invalid_input",
|
|
1116
|
+
operation: "bluesky.feeds.list",
|
|
1117
|
+
message: "Page size must be between 1 and 100.",
|
|
1118
|
+
});
|
|
646
1119
|
const query = new URLSearchParams({
|
|
647
|
-
limit: String(
|
|
1120
|
+
limit: String(limit),
|
|
1121
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
648
1122
|
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
649
1123
|
});
|
|
1124
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
650
1125
|
return object(await xrpc(`app.bsky.feed.getSuggestedFeeds?${query}`, context));
|
|
651
1126
|
},
|
|
652
1127
|
async listConversations(input) {
|
|
653
1128
|
const context = nativeContext(input.context, "bluesky.chat.list");
|
|
654
1129
|
assertNativeAccount(input.account, context, "bluesky.chat.list");
|
|
1130
|
+
const limit = input.limit ?? 50;
|
|
1131
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
1132
|
+
throw new SocialError({
|
|
1133
|
+
code: "invalid_input",
|
|
1134
|
+
operation: "bluesky.chat.list",
|
|
1135
|
+
message: "Page size must be between 1 and 100.",
|
|
1136
|
+
});
|
|
655
1137
|
const query = new URLSearchParams({
|
|
656
|
-
limit: String(
|
|
1138
|
+
limit: String(limit),
|
|
1139
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
657
1140
|
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
658
1141
|
});
|
|
1142
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
659
1143
|
return object(await xrpc(`chat.bsky.convo.listConvos?${query}`, context));
|
|
660
1144
|
},
|
|
661
1145
|
async listMessages(input) {
|
|
662
1146
|
const context = nativeContext(input.context, "bluesky.chat.messages");
|
|
663
1147
|
assertNativeAccount(input.account, context, "bluesky.chat.messages");
|
|
1148
|
+
const limit = input.limit ?? 50;
|
|
1149
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
|
|
1150
|
+
throw new SocialError({
|
|
1151
|
+
code: "invalid_input",
|
|
1152
|
+
operation: "bluesky.chat.messages",
|
|
1153
|
+
message: "Page size must be between 1 and 100.",
|
|
1154
|
+
});
|
|
664
1155
|
const query = new URLSearchParams({
|
|
665
|
-
limit: String(
|
|
1156
|
+
limit: String(limit),
|
|
1157
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
666
1158
|
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
667
1159
|
});
|
|
1160
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
668
1161
|
return object(await xrpc(`chat.bsky.convo.getMessages?convoId=${encodeURIComponent(input.conversationId)}&${query}`, context));
|
|
669
1162
|
},
|
|
670
1163
|
async sendMessage(input) {
|
|
@@ -676,6 +1169,7 @@ export function bluesky(options) {
|
|
|
676
1169
|
operation: "bluesky.chat.send",
|
|
677
1170
|
message: "Message text is required.",
|
|
678
1171
|
});
|
|
1172
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
679
1173
|
return object(await xrpc("chat.bsky.convo.sendMessage", context, {
|
|
680
1174
|
body: JSON.stringify({ convoId: input.conversationId, message: { text: input.text } }),
|
|
681
1175
|
}));
|
|
@@ -685,6 +1179,123 @@ export function bluesky(options) {
|
|
|
685
1179
|
id: backend,
|
|
686
1180
|
capabilities,
|
|
687
1181
|
native,
|
|
1182
|
+
graph: {
|
|
1183
|
+
async getProfile(account, input, context) {
|
|
1184
|
+
const value = await native.getProfile({
|
|
1185
|
+
account,
|
|
1186
|
+
...(input.profileId === undefined ? {} : { actor: input.profileId }),
|
|
1187
|
+
...(input.profileId === undefined && input.handle !== undefined
|
|
1188
|
+
? { actor: input.handle }
|
|
1189
|
+
: {}),
|
|
1190
|
+
context,
|
|
1191
|
+
});
|
|
1192
|
+
const actor = object(value);
|
|
1193
|
+
const id = string(actor["did"]);
|
|
1194
|
+
return {
|
|
1195
|
+
ref: profileRef({
|
|
1196
|
+
backend,
|
|
1197
|
+
platform: "bluesky",
|
|
1198
|
+
accountId: account.accountId,
|
|
1199
|
+
profileId: id,
|
|
1200
|
+
}),
|
|
1201
|
+
...(typeof actor["displayName"] === "string"
|
|
1202
|
+
? { displayName: actor["displayName"] }
|
|
1203
|
+
: {}),
|
|
1204
|
+
...(typeof actor["handle"] === "string" ? { handle: actor["handle"] } : {}),
|
|
1205
|
+
...(typeof actor["avatar"] === "string" ? { avatarUrl: actor["avatar"] } : {}),
|
|
1206
|
+
...(typeof actor["description"] === "string" ? { bio: actor["description"] } : {}),
|
|
1207
|
+
native: actor,
|
|
1208
|
+
};
|
|
1209
|
+
},
|
|
1210
|
+
async listRelationships(account, input, context) {
|
|
1211
|
+
const result = input.kind === "following"
|
|
1212
|
+
? await native.getFollows({
|
|
1213
|
+
account,
|
|
1214
|
+
context,
|
|
1215
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
1216
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
1217
|
+
})
|
|
1218
|
+
: input.kind === "followers"
|
|
1219
|
+
? await native.getFollowers({
|
|
1220
|
+
account,
|
|
1221
|
+
context,
|
|
1222
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
1223
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
1224
|
+
})
|
|
1225
|
+
: input.kind === "blocked"
|
|
1226
|
+
? await native.getBlocks({
|
|
1227
|
+
account,
|
|
1228
|
+
context,
|
|
1229
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
1230
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
1231
|
+
})
|
|
1232
|
+
: await native.getMutes({
|
|
1233
|
+
account,
|
|
1234
|
+
context,
|
|
1235
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
1236
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
1237
|
+
});
|
|
1238
|
+
return {
|
|
1239
|
+
items: result.actors.map((actor) => {
|
|
1240
|
+
const value = object(actor);
|
|
1241
|
+
const id = string(value["did"]);
|
|
1242
|
+
return {
|
|
1243
|
+
profile: profileRef({
|
|
1244
|
+
backend,
|
|
1245
|
+
platform: "bluesky",
|
|
1246
|
+
accountId: account.accountId,
|
|
1247
|
+
profileId: id,
|
|
1248
|
+
}),
|
|
1249
|
+
relationship: input.kind === "following"
|
|
1250
|
+
? "following"
|
|
1251
|
+
: input.kind === "followers"
|
|
1252
|
+
? "follower"
|
|
1253
|
+
: input.kind,
|
|
1254
|
+
};
|
|
1255
|
+
}),
|
|
1256
|
+
...(result.cursor === undefined ? {} : { nextCursor: result.cursor }),
|
|
1257
|
+
};
|
|
1258
|
+
},
|
|
1259
|
+
async follow(target, context) {
|
|
1260
|
+
await native.follow({ account, did: target.profileId, context });
|
|
1261
|
+
return { profile: target, relationship: "following" };
|
|
1262
|
+
},
|
|
1263
|
+
async unfollow(target, context) {
|
|
1264
|
+
const profile = object(await native.getProfile({ account, actor: target.profileId, context }));
|
|
1265
|
+
const viewer = object(profile["viewer"] ?? {});
|
|
1266
|
+
const uri = viewer["following"];
|
|
1267
|
+
if (typeof uri !== "string" || !uri)
|
|
1268
|
+
throw new SocialError({
|
|
1269
|
+
code: "invalid_input",
|
|
1270
|
+
operation: "graph.unfollow",
|
|
1271
|
+
message: "A follow record URI is required to unfollow.",
|
|
1272
|
+
});
|
|
1273
|
+
await native.unfollow({ account, followUri: uri, context });
|
|
1274
|
+
},
|
|
1275
|
+
async block(target, context) {
|
|
1276
|
+
await native.block({ account, did: target.profileId, context });
|
|
1277
|
+
return { profile: target, relationship: "blocked" };
|
|
1278
|
+
},
|
|
1279
|
+
async unblock(target, context) {
|
|
1280
|
+
const profile = object(await native.getProfile({ account, actor: target.profileId, context }));
|
|
1281
|
+
const viewer = object(profile["viewer"] ?? {});
|
|
1282
|
+
const uri = viewer["blocking"];
|
|
1283
|
+
if (typeof uri !== "string" || !uri)
|
|
1284
|
+
throw new SocialError({
|
|
1285
|
+
code: "invalid_input",
|
|
1286
|
+
operation: "graph.unblock",
|
|
1287
|
+
message: "A block record URI is required to unblock.",
|
|
1288
|
+
});
|
|
1289
|
+
await native.unblock({ account, blockUri: uri, context });
|
|
1290
|
+
},
|
|
1291
|
+
async mute(target, context) {
|
|
1292
|
+
await native.mute({ account, did: target.profileId, context });
|
|
1293
|
+
return { profile: target, relationship: "muted" };
|
|
1294
|
+
},
|
|
1295
|
+
async unmute(target, context) {
|
|
1296
|
+
await native.unmute({ account, did: target.profileId, context });
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
688
1299
|
accounts: {
|
|
689
1300
|
async list(_input, context) {
|
|
690
1301
|
const session = await verifiedSession(context);
|
|
@@ -693,6 +1304,7 @@ export function bluesky(options) {
|
|
|
693
1304
|
{
|
|
694
1305
|
ref: account,
|
|
695
1306
|
displayName: session.handle ?? session.did,
|
|
1307
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
696
1308
|
...(session.handle === undefined ? {} : { handle: session.handle }),
|
|
697
1309
|
status: "connected",
|
|
698
1310
|
},
|
|
@@ -711,11 +1323,85 @@ export function bluesky(options) {
|
|
|
711
1323
|
return {
|
|
712
1324
|
ref: account,
|
|
713
1325
|
displayName: session.handle ?? session.did,
|
|
1326
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
714
1327
|
...(session.handle === undefined ? {} : { handle: session.handle }),
|
|
715
1328
|
status: "connected",
|
|
716
1329
|
};
|
|
717
1330
|
},
|
|
718
1331
|
},
|
|
1332
|
+
search: {
|
|
1333
|
+
async posts(account, input, context) {
|
|
1334
|
+
if (!accountMatches(account, auth, backend))
|
|
1335
|
+
throw new SocialError({
|
|
1336
|
+
code: "unauthorized",
|
|
1337
|
+
operation: "search.posts",
|
|
1338
|
+
message: "The account reference does not belong to this adapter.",
|
|
1339
|
+
account,
|
|
1340
|
+
});
|
|
1341
|
+
const result = await native.searchPosts({
|
|
1342
|
+
account,
|
|
1343
|
+
query: input.query,
|
|
1344
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
1345
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
1346
|
+
...(input.startTime === undefined ? {} : { since: input.startTime }),
|
|
1347
|
+
...(input.endTime === undefined ? {} : { until: input.endTime }),
|
|
1348
|
+
...(input.scope === undefined ? {} : { scope: input.scope }),
|
|
1349
|
+
context,
|
|
1350
|
+
});
|
|
1351
|
+
return {
|
|
1352
|
+
items: result.posts,
|
|
1353
|
+
...(result.cursor === undefined ? {} : { nextCursor: result.cursor }),
|
|
1354
|
+
...(result.hitsTotal === undefined ? {} : { metadata: { hitsTotal: result.hitsTotal } }),
|
|
1355
|
+
};
|
|
1356
|
+
},
|
|
1357
|
+
},
|
|
1358
|
+
notifications: {
|
|
1359
|
+
async list(account, input, context) {
|
|
1360
|
+
if (!accountMatches(account, auth, backend))
|
|
1361
|
+
throw new SocialError({
|
|
1362
|
+
code: "unauthorized",
|
|
1363
|
+
operation: "bluesky.notifications.list",
|
|
1364
|
+
message: "The account reference does not belong to this adapter.",
|
|
1365
|
+
account,
|
|
1366
|
+
});
|
|
1367
|
+
const response = await native.listNotifications({
|
|
1368
|
+
account,
|
|
1369
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
1370
|
+
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
1371
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
1372
|
+
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
1373
|
+
context,
|
|
1374
|
+
});
|
|
1375
|
+
const values = response["notifications"];
|
|
1376
|
+
const items = values === undefined
|
|
1377
|
+
? []
|
|
1378
|
+
: array(values).map((value) => {
|
|
1379
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated provider notification object.
|
|
1380
|
+
return object(value);
|
|
1381
|
+
});
|
|
1382
|
+
const cursor = response["cursor"];
|
|
1383
|
+
return {
|
|
1384
|
+
items,
|
|
1385
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
1386
|
+
...(typeof cursor === "string" ? { nextCursor: cursor } : {}),
|
|
1387
|
+
};
|
|
1388
|
+
},
|
|
1389
|
+
async markSeen(account, input, context) {
|
|
1390
|
+
if (!accountMatches(account, auth, backend))
|
|
1391
|
+
throw new SocialError({
|
|
1392
|
+
code: "unauthorized",
|
|
1393
|
+
operation: "bluesky.notifications.seen",
|
|
1394
|
+
message: "The account reference does not belong to this adapter.",
|
|
1395
|
+
account,
|
|
1396
|
+
});
|
|
1397
|
+
await native.markNotificationsSeen({
|
|
1398
|
+
account,
|
|
1399
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
1400
|
+
...(input.seenAt === undefined ? {} : { seenAt: input.seenAt }),
|
|
1401
|
+
context,
|
|
1402
|
+
});
|
|
1403
|
+
},
|
|
1404
|
+
},
|
|
719
1405
|
posts: {
|
|
720
1406
|
async list(account, input, context) {
|
|
721
1407
|
if (!accountMatches(account, auth, backend))
|
|
@@ -734,6 +1420,7 @@ export function bluesky(options) {
|
|
|
734
1420
|
const query = new URLSearchParams({
|
|
735
1421
|
actor: auth.did,
|
|
736
1422
|
limit: String(limit),
|
|
1423
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
737
1424
|
...(input.cursor === undefined ? {} : { cursor: input.cursor }),
|
|
738
1425
|
});
|
|
739
1426
|
const response = object(await xrpc(`app.bsky.feed.getAuthorFeed?${query.toString()}`, context));
|
|
@@ -751,6 +1438,7 @@ export function bluesky(options) {
|
|
|
751
1438
|
"indexedAt",
|
|
752
1439
|
"labels",
|
|
753
1440
|
]),
|
|
1441
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
754
1442
|
...(post["author"] === undefined
|
|
755
1443
|
? {}
|
|
756
1444
|
: {
|
|
@@ -761,6 +1449,7 @@ export function bluesky(options) {
|
|
|
761
1449
|
"avatar",
|
|
762
1450
|
]),
|
|
763
1451
|
}),
|
|
1452
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
764
1453
|
...(post["record"] === undefined
|
|
765
1454
|
? {}
|
|
766
1455
|
: {
|
|
@@ -773,16 +1462,21 @@ export function bluesky(options) {
|
|
|
773
1462
|
]),
|
|
774
1463
|
}),
|
|
775
1464
|
};
|
|
1465
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
776
1466
|
return {
|
|
777
1467
|
post: safePost,
|
|
1468
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
778
1469
|
...(entry["reason"] === undefined
|
|
779
1470
|
? {}
|
|
780
1471
|
: { reason: publicFields(object(entry["reason"]), ["$type", "by", "indexedAt"]) }),
|
|
781
1472
|
};
|
|
782
1473
|
});
|
|
1474
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
783
1475
|
const cursor = typeof response["cursor"] === "string" ? response["cursor"] : undefined;
|
|
784
1476
|
return {
|
|
1477
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
785
1478
|
items: feed,
|
|
1479
|
+
// oxlint-disable-next-line anti-slop/no-conditional-empty-object-spread -- validated boundary or fixture contract.
|
|
786
1480
|
...(cursor === undefined ? {} : { nextCursor: cursor }),
|
|
787
1481
|
};
|
|
788
1482
|
},
|
|
@@ -842,6 +1536,14 @@ export function bluesky(options) {
|
|
|
842
1536
|
try {
|
|
843
1537
|
const content = target.content;
|
|
844
1538
|
const text = content.text ?? "";
|
|
1539
|
+
// oxlint-disable-next-line anti-slop/no-known-value-widening -- validated boundary or fixture contract.
|
|
1540
|
+
// oxlint-disable-next-line anti-slop/no-unsafe-dictionary-type -- validated boundary or fixture contract.
|
|
1541
|
+
// oxlint-disable-next-line anti-slop/no-known-value-widening -- provider payload is validated at this adapter boundary.
|
|
1542
|
+
// oxlint-disable-next-line anti-slop/no-unsafe-dictionary-type -- validated external boundary or fixture contract.
|
|
1543
|
+
// oxlint-disable-next-line anti-slop/no-known-value-widening -- validated external boundary or fixture contract.
|
|
1544
|
+
// oxlint-disable-next-line anti-slop/no-unsafe-dictionary-type -- validated external boundary or fixture contract.
|
|
1545
|
+
// oxlint-disable-next-line anti-slop/no-known-value-widening -- validated external boundary or fixture contract.
|
|
1546
|
+
// oxlint-disable-next-line anti-slop/no-unsafe-dictionary-type -- validated external boundary or fixture contract.
|
|
845
1547
|
const record = {
|
|
846
1548
|
$type: "app.bsky.feed.post",
|
|
847
1549
|
text,
|
|
@@ -871,10 +1573,12 @@ export function bluesky(options) {
|
|
|
871
1573
|
Object.assign(record, { reply: { root, parent: parentRef } });
|
|
872
1574
|
}
|
|
873
1575
|
if (content.media !== undefined && content.media.length > 0) {
|
|
1576
|
+
// oxlint-disable-next-line anti-slop/no-unsafe-dictionary-type -- validated boundary or fixture contract.
|
|
874
1577
|
const blobs = [];
|
|
875
1578
|
for (const media of content.media) {
|
|
876
1579
|
const source = await readMedia(media.source, fetcher, context, allowMediaHost);
|
|
877
1580
|
const blob = object(await xrpc("com.atproto.repo.uploadBlob", context, {
|
|
1581
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
878
1582
|
body: new Blob([source.bytes.slice().buffer], {
|
|
879
1583
|
type: source.mimeType,
|
|
880
1584
|
}),
|
|
@@ -935,6 +1639,26 @@ export function bluesky(options) {
|
|
|
935
1639
|
});
|
|
936
1640
|
return native.getPost({ uri: ref.postId, context });
|
|
937
1641
|
},
|
|
1642
|
+
async removeFromPlatform(ref, context) {
|
|
1643
|
+
if (!accountMatches(connectedAccountRef({
|
|
1644
|
+
backend: ref.backend,
|
|
1645
|
+
platform: "bluesky",
|
|
1646
|
+
accountId: ref.accountId,
|
|
1647
|
+
}), auth, backend))
|
|
1648
|
+
throw new SocialError({
|
|
1649
|
+
code: "unauthorized",
|
|
1650
|
+
operation: "posts.removeFromPlatform",
|
|
1651
|
+
message: "The post reference does not belong to this Bluesky adapter.",
|
|
1652
|
+
});
|
|
1653
|
+
await native.deletePost({
|
|
1654
|
+
account,
|
|
1655
|
+
post: {
|
|
1656
|
+
uri: ref.native?.["uri"]?.toString() ?? ref.postId,
|
|
1657
|
+
cid: ref.native?.["cid"]?.toString() ?? "",
|
|
1658
|
+
},
|
|
1659
|
+
context,
|
|
1660
|
+
});
|
|
1661
|
+
},
|
|
938
1662
|
},
|
|
939
1663
|
comments: {
|
|
940
1664
|
async list(post, input, context) {
|
|
@@ -946,6 +1670,7 @@ export function bluesky(options) {
|
|
|
946
1670
|
});
|
|
947
1671
|
const thread = await native.getPostThread({ uri: post.postId, context });
|
|
948
1672
|
const replies = array(object(thread["thread"])["replies"] ?? []);
|
|
1673
|
+
// oxlint-disable-next-line anti-slop/require-safety-comment-for-type-assertion -- validated boundary or fixture contract.
|
|
949
1674
|
return { items: replies.map((reply) => object(reply)) };
|
|
950
1675
|
},
|
|
951
1676
|
async reply(comment, content, context) {
|
|
@@ -995,6 +1720,7 @@ export function bluesky(options) {
|
|
|
995
1720
|
["quotes", "quoteCount"],
|
|
996
1721
|
]) {
|
|
997
1722
|
const count = record[field];
|
|
1723
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
998
1724
|
if (typeof count === "number" && Number.isFinite(count))
|
|
999
1725
|
metrics.push({
|
|
1000
1726
|
name,
|
|
@@ -1028,7 +1754,9 @@ export function bluesky(options) {
|
|
|
1028
1754
|
["followers", profile["followersCount"]],
|
|
1029
1755
|
["following", profile["followsCount"]],
|
|
1030
1756
|
["posts", profile["postsCount"]],
|
|
1031
|
-
].flatMap(([name, value]) =>
|
|
1757
|
+
].flatMap(([name, value]) =>
|
|
1758
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- validated boundary or fixture contract.
|
|
1759
|
+
typeof value === "number" && Number.isFinite(value)
|
|
1032
1760
|
? [
|
|
1033
1761
|
{
|
|
1034
1762
|
name,
|