birdclaw 0.5.1 → 0.7.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/CHANGELOG.md +92 -1
- package/README.md +75 -5
- package/package.json +8 -2
- package/scripts/browser-perf.mjs +1 -0
- package/scripts/start-test-server.mjs +16 -3
- package/src/cli.ts +812 -37
- package/src/components/AccountSwitcher.tsx +186 -0
- package/src/components/AppNav.tsx +37 -7
- package/src/components/AvatarChip.tsx +9 -3
- package/src/components/DmWorkspace.tsx +18 -8
- package/src/components/LinkPreviewCard.tsx +40 -18
- package/src/components/MarkdownViewer.tsx +818 -0
- package/src/components/ProfileAnalysisStream.tsx +428 -0
- package/src/components/ProfilePreview.tsx +120 -9
- package/src/components/SavedTimelineView.tsx +30 -8
- package/src/components/SyncNowButton.tsx +60 -25
- package/src/components/ThemeSlider.tsx +55 -50
- package/src/components/TimelineCard.tsx +240 -92
- package/src/components/TimelineRouteFrame.tsx +38 -8
- package/src/components/TweetMediaGrid.tsx +87 -38
- package/src/components/TweetRichText.tsx +45 -17
- package/src/components/account-selection.ts +64 -0
- package/src/components/useTimelineRouteData.ts +97 -13
- package/src/lib/account-sync-job.ts +666 -0
- package/src/lib/actions-transport.ts +216 -146
- package/src/lib/api-client.ts +128 -53
- package/src/lib/archive-finder.ts +78 -63
- package/src/lib/archive-import.ts +1593 -1291
- package/src/lib/authored-live.ts +262 -204
- package/src/lib/avatar-cache.ts +208 -43
- package/src/lib/backup.ts +1536 -954
- package/src/lib/bird-actions.ts +139 -57
- package/src/lib/bird-command.ts +101 -28
- package/src/lib/bird.ts +582 -194
- package/src/lib/blocklist.ts +40 -23
- package/src/lib/blocks-write.ts +129 -80
- package/src/lib/blocks.ts +165 -97
- package/src/lib/bookmark-sync-job.ts +250 -160
- package/src/lib/config.ts +35 -2
- package/src/lib/conversation-surface.ts +79 -48
- package/src/lib/data-sources.ts +219 -0
- package/src/lib/db.ts +95 -4
- package/src/lib/dms-live.ts +720 -66
- package/src/lib/effect-runtime.ts +45 -0
- package/src/lib/follow-graph.ts +224 -180
- package/src/lib/geocoding.ts +296 -0
- package/src/lib/http-effect.ts +222 -0
- package/src/lib/inbox.ts +74 -43
- package/src/lib/link-index.ts +88 -76
- package/src/lib/link-insights.ts +24 -0
- package/src/lib/link-preview-metadata.ts +472 -52
- package/src/lib/location.ts +137 -0
- package/src/lib/media-fetch.ts +286 -213
- package/src/lib/mention-threads-live.ts +445 -288
- package/src/lib/mentions-live.ts +549 -354
- package/src/lib/moderation-target.ts +102 -65
- package/src/lib/moderation-write.ts +77 -18
- package/src/lib/mutes-write.ts +129 -80
- package/src/lib/mutes.ts +8 -1
- package/src/lib/network-map.ts +382 -0
- package/src/lib/openai.ts +84 -53
- package/src/lib/period-digest.ts +1317 -0
- package/src/lib/profile-affiliation-hydration.ts +93 -54
- package/src/lib/profile-analysis.ts +1272 -0
- package/src/lib/profile-bio-entities.ts +1 -1
- package/src/lib/profile-hydration.ts +124 -72
- package/src/lib/profile-replies.ts +60 -43
- package/src/lib/profile-resolver.ts +402 -294
- package/src/lib/queries.ts +983 -203
- package/src/lib/research.ts +165 -120
- package/src/lib/search-discussion.ts +1016 -0
- package/src/lib/sqlite.ts +1 -0
- package/src/lib/timeline-collections-live.ts +204 -167
- package/src/lib/timeline-live.ts +325 -51
- package/src/lib/tweet-account-edges.ts +2 -0
- package/src/lib/tweet-lookup.ts +30 -19
- package/src/lib/tweet-render.ts +141 -1
- package/src/lib/tweet-search-live.ts +565 -0
- package/src/lib/types.ts +75 -3
- package/src/lib/ui.ts +31 -8
- package/src/lib/url-expansion.ts +226 -55
- package/src/lib/url-safety.ts +220 -0
- package/src/lib/web-sync.ts +222 -149
- package/src/lib/whois.ts +166 -147
- package/src/lib/x-web.ts +102 -71
- package/src/lib/xurl-rate-limits.ts +267 -0
- package/src/lib/xurl.ts +1185 -405
- package/src/routeTree.gen.ts +273 -0
- package/src/routes/__root.tsx +24 -5
- package/src/routes/api/action.tsx +127 -78
- package/src/routes/api/avatar.tsx +39 -30
- package/src/routes/api/blocks.tsx +26 -23
- package/src/routes/api/conversation.tsx +25 -14
- package/src/routes/api/data-sources.tsx +24 -0
- package/src/routes/api/inbox.tsx +27 -21
- package/src/routes/api/link-insights.tsx +31 -25
- package/src/routes/api/link-preview.tsx +25 -21
- package/src/routes/api/network-map.tsx +55 -0
- package/src/routes/api/period-digest.tsx +133 -0
- package/src/routes/api/profile-analysis.tsx +152 -0
- package/src/routes/api/profile-hydrate.tsx +31 -28
- package/src/routes/api/query.tsx +80 -55
- package/src/routes/api/search-discussion.tsx +169 -0
- package/src/routes/api/status.tsx +18 -10
- package/src/routes/api/sync.tsx +75 -29
- package/src/routes/api/xurl-rate-limits.tsx +24 -0
- package/src/routes/data-sources.tsx +255 -0
- package/src/routes/discuss.tsx +419 -0
- package/src/routes/dms.tsx +95 -28
- package/src/routes/inbox.tsx +32 -19
- package/src/routes/network-map.tsx +1035 -0
- package/src/routes/profile-analyze.tsx +112 -0
- package/src/routes/profiles.$handle.tsx +228 -0
- package/src/routes/rate-limits.tsx +309 -0
- package/src/routes/today.tsx +455 -0
- package/src/styles.css +22 -0
package/src/lib/bird.ts
CHANGED
|
@@ -3,7 +3,9 @@ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
+
import { Effect } from "effect";
|
|
6
7
|
import { getBirdCommand } from "./config";
|
|
8
|
+
import { runEffectPromise } from "./effect-runtime";
|
|
7
9
|
import type {
|
|
8
10
|
XurlMentionData,
|
|
9
11
|
XurlFollowUsersResponse,
|
|
@@ -15,6 +17,7 @@ import type {
|
|
|
15
17
|
|
|
16
18
|
const execFileAsync = promisify(execFile);
|
|
17
19
|
const BIRD_JSON_MAX_BUFFER_BYTES = 512 * 1024 * 1024;
|
|
20
|
+
const BIRD_STDOUT_REDIRECT_SCRIPT = 'out="$1"; shift; exec "$@" > "$out"';
|
|
18
21
|
|
|
19
22
|
interface BirdTweetMedia {
|
|
20
23
|
type?: string;
|
|
@@ -36,7 +39,9 @@ interface BirdTweetItem {
|
|
|
36
39
|
conversationId?: string;
|
|
37
40
|
inReplyToStatusId?: string | null;
|
|
38
41
|
quotedStatusId?: string | null;
|
|
42
|
+
retweetedStatusId?: string | null;
|
|
39
43
|
quotedTweet?: { id?: string | null } | null;
|
|
44
|
+
retweetedTweet?: { id?: string | null } | null;
|
|
40
45
|
author?: BirdTweetAuthor;
|
|
41
46
|
authorId?: string;
|
|
42
47
|
media?: BirdTweetMedia[];
|
|
@@ -58,6 +63,8 @@ export interface BirdDmEvent {
|
|
|
58
63
|
recipientId?: string;
|
|
59
64
|
sender?: BirdDmUser;
|
|
60
65
|
recipient?: BirdDmUser;
|
|
66
|
+
inboxKind?: "accepted" | "request";
|
|
67
|
+
isMessageRequest?: boolean;
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
export interface BirdDmConversation {
|
|
@@ -66,6 +73,8 @@ export interface BirdDmConversation {
|
|
|
66
73
|
messages: BirdDmEvent[];
|
|
67
74
|
lastMessageAt?: string;
|
|
68
75
|
lastMessagePreview?: string;
|
|
76
|
+
inboxKind?: "accepted" | "request";
|
|
77
|
+
isMessageRequest?: boolean;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
export interface BirdDmsResponse {
|
|
@@ -74,6 +83,27 @@ export interface BirdDmsResponse {
|
|
|
74
83
|
events: BirdDmEvent[];
|
|
75
84
|
}
|
|
76
85
|
|
|
86
|
+
export interface BirdAuthenticatedAccount {
|
|
87
|
+
id?: string;
|
|
88
|
+
username: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type BirdDmRequestAction = "accept" | "reject" | "block";
|
|
92
|
+
|
|
93
|
+
export type BirdDmMutationResponse =
|
|
94
|
+
| {
|
|
95
|
+
success: true;
|
|
96
|
+
conversationId?: string;
|
|
97
|
+
userId?: string;
|
|
98
|
+
username?: string;
|
|
99
|
+
blockedUserId?: string;
|
|
100
|
+
blockedUsername?: string;
|
|
101
|
+
}
|
|
102
|
+
| {
|
|
103
|
+
success: false;
|
|
104
|
+
error: string;
|
|
105
|
+
};
|
|
106
|
+
|
|
77
107
|
interface BirdUserOverviewPayload {
|
|
78
108
|
user?: {
|
|
79
109
|
id?: string;
|
|
@@ -223,23 +253,90 @@ function isUnsupportedBirdOptionError(error: unknown, option: string) {
|
|
|
223
253
|
return text.includes(option) && /unknown option|error:/i.test(text);
|
|
224
254
|
}
|
|
225
255
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
256
|
+
function makeBirdStdoutTempEffect() {
|
|
257
|
+
return Effect.acquireRelease(
|
|
258
|
+
Effect.sync(() => {
|
|
259
|
+
const tempDir = mkdtempSync(join(tmpdir(), "birdclaw-bird-"));
|
|
260
|
+
return { tempDir, stdoutPath: join(tempDir, "stdout.json") };
|
|
261
|
+
}),
|
|
262
|
+
({ tempDir }) =>
|
|
263
|
+
Effect.sync(() => rmSync(tempDir, { recursive: true, force: true })),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function runBirdJsonCommandEffect(args: string[], timeoutMs?: number) {
|
|
268
|
+
return Effect.scoped(
|
|
269
|
+
Effect.gen(function* () {
|
|
270
|
+
const birdCommand = yield* Effect.try({
|
|
271
|
+
try: () => getBirdCommand(),
|
|
272
|
+
catch: (error) =>
|
|
273
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
274
|
+
});
|
|
275
|
+
const { stdoutPath } = yield* makeBirdStdoutTempEffect();
|
|
276
|
+
yield* Effect.tryPromise({
|
|
277
|
+
try: () =>
|
|
278
|
+
execFileAsync(
|
|
279
|
+
"/bin/bash",
|
|
280
|
+
[
|
|
281
|
+
"-c",
|
|
282
|
+
BIRD_STDOUT_REDIRECT_SCRIPT,
|
|
283
|
+
"birdclaw-bird",
|
|
284
|
+
stdoutPath,
|
|
285
|
+
birdCommand,
|
|
286
|
+
...args,
|
|
287
|
+
],
|
|
288
|
+
{ maxBuffer: BIRD_JSON_MAX_BUFFER_BYTES, timeout: timeoutMs },
|
|
289
|
+
),
|
|
290
|
+
catch: (error) => formatBirdCommandError(error, birdCommand),
|
|
291
|
+
});
|
|
292
|
+
return yield* Effect.try({
|
|
293
|
+
try: () => readFileSync(stdoutPath, "utf8"),
|
|
294
|
+
catch: (error) => error,
|
|
295
|
+
});
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function runBirdJsonCommandAllowFailureEffect(
|
|
301
|
+
args: string[],
|
|
302
|
+
timeoutMs?: number,
|
|
303
|
+
) {
|
|
304
|
+
return Effect.scoped(
|
|
305
|
+
Effect.gen(function* () {
|
|
306
|
+
const birdCommand = yield* Effect.try({
|
|
307
|
+
try: () => getBirdCommand(),
|
|
308
|
+
catch: (error) =>
|
|
309
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
310
|
+
});
|
|
311
|
+
const { stdoutPath } = yield* makeBirdStdoutTempEffect();
|
|
312
|
+
yield* Effect.tryPromise({
|
|
313
|
+
try: () =>
|
|
314
|
+
execFileAsync(
|
|
315
|
+
"/bin/bash",
|
|
316
|
+
[
|
|
317
|
+
"-c",
|
|
318
|
+
BIRD_STDOUT_REDIRECT_SCRIPT,
|
|
319
|
+
"birdclaw-bird",
|
|
320
|
+
stdoutPath,
|
|
321
|
+
birdCommand,
|
|
322
|
+
...args,
|
|
323
|
+
],
|
|
324
|
+
{ maxBuffer: BIRD_JSON_MAX_BUFFER_BYTES, timeout: timeoutMs },
|
|
325
|
+
).catch((error: unknown) => {
|
|
326
|
+
const stdout = readFileSync(stdoutPath, "utf8");
|
|
327
|
+
if (stdout.trim().length > 0) {
|
|
328
|
+
return { stdout: "", stderr: "" };
|
|
329
|
+
}
|
|
330
|
+
throw formatBirdCommandError(error, birdCommand);
|
|
331
|
+
}),
|
|
332
|
+
catch: (error) => error,
|
|
333
|
+
});
|
|
334
|
+
return yield* Effect.try({
|
|
335
|
+
try: () => readFileSync(stdoutPath, "utf8"),
|
|
336
|
+
catch: (error) => error,
|
|
337
|
+
});
|
|
338
|
+
}),
|
|
339
|
+
);
|
|
243
340
|
}
|
|
244
341
|
|
|
245
342
|
function getBirdTweetItems(payload: unknown, command: string) {
|
|
@@ -304,6 +401,16 @@ function toReferencedTweets(item: BirdTweetItem) {
|
|
|
304
401
|
references.push({ type: "quoted", id: quotedTweetId });
|
|
305
402
|
}
|
|
306
403
|
|
|
404
|
+
const retweetedTweetId =
|
|
405
|
+
typeof item.retweetedStatusId === "string" && item.retweetedStatusId
|
|
406
|
+
? item.retweetedStatusId
|
|
407
|
+
: typeof item.retweetedTweet?.id === "string" && item.retweetedTweet.id
|
|
408
|
+
? item.retweetedTweet.id
|
|
409
|
+
: null;
|
|
410
|
+
if (retweetedTweetId) {
|
|
411
|
+
references.push({ type: "retweeted", id: retweetedTweetId });
|
|
412
|
+
}
|
|
413
|
+
|
|
307
414
|
return references.length > 0 ? references : undefined;
|
|
308
415
|
}
|
|
309
416
|
|
|
@@ -352,23 +459,51 @@ function normalizeBirdTweets(items: BirdTweetItem[]): XurlMentionsResponse {
|
|
|
352
459
|
};
|
|
353
460
|
}
|
|
354
461
|
|
|
355
|
-
|
|
462
|
+
function parseBirdJsonEffect(stdout: string) {
|
|
463
|
+
return Effect.try({
|
|
464
|
+
try: () => parseBirdJson(stdout),
|
|
465
|
+
catch: (error) => error,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function normalizeBirdTweetsPayloadEffect(payload: unknown, command: string) {
|
|
470
|
+
return Effect.try({
|
|
471
|
+
try: () => normalizeBirdTweets(getBirdTweetItems(payload, command)),
|
|
472
|
+
catch: (error) => error,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function normalizeBirdTweetItemEffect(payload: unknown, command: string) {
|
|
477
|
+
return Effect.try({
|
|
478
|
+
try: () => getBirdTweetItem(payload, command),
|
|
479
|
+
catch: (error) => error,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function listMentionsViaBirdEffect({
|
|
356
484
|
maxResults,
|
|
357
485
|
}: {
|
|
358
486
|
maxResults: number;
|
|
359
|
-
}):
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
487
|
+
}): Effect.Effect<XurlMentionsResponse, unknown> {
|
|
488
|
+
return Effect.gen(function* () {
|
|
489
|
+
const stdout = yield* runBirdJsonCommandEffect([
|
|
490
|
+
"mentions",
|
|
491
|
+
"-n",
|
|
492
|
+
String(maxResults),
|
|
493
|
+
"--json",
|
|
494
|
+
]);
|
|
495
|
+
const payload = yield* parseBirdJsonEffect(stdout);
|
|
496
|
+
return yield* normalizeBirdTweetsPayloadEffect(payload, "mentions");
|
|
497
|
+
});
|
|
498
|
+
}
|
|
367
499
|
|
|
368
|
-
|
|
500
|
+
export function listMentionsViaBird(options: {
|
|
501
|
+
maxResults: number;
|
|
502
|
+
}): Promise<XurlMentionsResponse> {
|
|
503
|
+
return runEffectPromise(listMentionsViaBirdEffect(options));
|
|
369
504
|
}
|
|
370
505
|
|
|
371
|
-
|
|
506
|
+
function listTweetsViaBirdCommandEffect({
|
|
372
507
|
command,
|
|
373
508
|
maxResults,
|
|
374
509
|
all,
|
|
@@ -378,86 +513,147 @@ async function listTweetsViaBirdCommand({
|
|
|
378
513
|
maxResults: number;
|
|
379
514
|
all?: boolean;
|
|
380
515
|
maxPages?: number;
|
|
381
|
-
}):
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
516
|
+
}): Effect.Effect<XurlMentionsResponse, unknown> {
|
|
517
|
+
return Effect.gen(function* () {
|
|
518
|
+
const args = [command, "-n", String(maxResults), "--json"];
|
|
519
|
+
if (all) {
|
|
520
|
+
args.push("--all");
|
|
521
|
+
}
|
|
522
|
+
if (maxPages !== undefined) {
|
|
523
|
+
args.push("--max-pages", String(maxPages));
|
|
524
|
+
}
|
|
525
|
+
const stdout = yield* runBirdJsonCommandEffect(args);
|
|
526
|
+
const payload = yield* parseBirdJsonEffect(stdout);
|
|
527
|
+
return yield* normalizeBirdTweetsPayloadEffect(payload, command);
|
|
528
|
+
});
|
|
393
529
|
}
|
|
394
530
|
|
|
395
|
-
export
|
|
396
|
-
maxResults,
|
|
397
|
-
all,
|
|
398
|
-
maxPages,
|
|
399
|
-
}: {
|
|
531
|
+
export function listLikedTweetsViaBirdEffect(options: {
|
|
400
532
|
maxResults: number;
|
|
401
533
|
all?: boolean;
|
|
402
534
|
maxPages?: number;
|
|
403
|
-
}):
|
|
404
|
-
return
|
|
535
|
+
}): Effect.Effect<XurlMentionsResponse, unknown> {
|
|
536
|
+
return listTweetsViaBirdCommandEffect({
|
|
405
537
|
command: "likes",
|
|
406
|
-
|
|
407
|
-
all,
|
|
408
|
-
maxPages,
|
|
538
|
+
...options,
|
|
409
539
|
});
|
|
410
540
|
}
|
|
411
541
|
|
|
412
|
-
export
|
|
413
|
-
maxResults,
|
|
414
|
-
all,
|
|
415
|
-
maxPages,
|
|
416
|
-
}: {
|
|
542
|
+
export function listLikedTweetsViaBird(options: {
|
|
417
543
|
maxResults: number;
|
|
418
544
|
all?: boolean;
|
|
419
545
|
maxPages?: number;
|
|
420
546
|
}): Promise<XurlMentionsResponse> {
|
|
421
|
-
return
|
|
547
|
+
return runEffectPromise(listLikedTweetsViaBirdEffect(options));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export function listBookmarkedTweetsViaBirdEffect(options: {
|
|
551
|
+
maxResults: number;
|
|
552
|
+
all?: boolean;
|
|
553
|
+
maxPages?: number;
|
|
554
|
+
}): Effect.Effect<XurlMentionsResponse, unknown> {
|
|
555
|
+
return listTweetsViaBirdCommandEffect({
|
|
422
556
|
command: "bookmarks",
|
|
423
|
-
|
|
424
|
-
all,
|
|
425
|
-
maxPages,
|
|
557
|
+
...options,
|
|
426
558
|
});
|
|
427
559
|
}
|
|
428
560
|
|
|
429
|
-
export
|
|
561
|
+
export function listBookmarkedTweetsViaBird(options: {
|
|
562
|
+
maxResults: number;
|
|
563
|
+
all?: boolean;
|
|
564
|
+
maxPages?: number;
|
|
565
|
+
}): Promise<XurlMentionsResponse> {
|
|
566
|
+
return runEffectPromise(listBookmarkedTweetsViaBirdEffect(options));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export function searchTweetsViaBirdEffect(
|
|
570
|
+
query: string,
|
|
571
|
+
options: {
|
|
572
|
+
maxResults: number;
|
|
573
|
+
all?: boolean;
|
|
574
|
+
maxPages?: number;
|
|
575
|
+
},
|
|
576
|
+
): Effect.Effect<XurlMentionsResponse, unknown> {
|
|
577
|
+
return Effect.gen(function* () {
|
|
578
|
+
const args = ["search", query, "-n", String(options.maxResults), "--json"];
|
|
579
|
+
if (options.all) {
|
|
580
|
+
args.push("--all");
|
|
581
|
+
}
|
|
582
|
+
if (options.all && options.maxPages !== undefined) {
|
|
583
|
+
args.push("--max-pages", String(options.maxPages));
|
|
584
|
+
}
|
|
585
|
+
const stdout = yield* runBirdJsonCommandEffect(args);
|
|
586
|
+
const payload = yield* parseBirdJsonEffect(stdout);
|
|
587
|
+
return yield* normalizeBirdTweetsPayloadEffect(payload, "search");
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export function searchTweetsViaBird(
|
|
592
|
+
query: string,
|
|
593
|
+
options: {
|
|
594
|
+
maxResults: number;
|
|
595
|
+
all?: boolean;
|
|
596
|
+
maxPages?: number;
|
|
597
|
+
},
|
|
598
|
+
): Promise<XurlMentionsResponse> {
|
|
599
|
+
return runEffectPromise(searchTweetsViaBirdEffect(query, options));
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export function lookupTweetsByIdsViaBirdEffect(
|
|
430
603
|
ids: string[],
|
|
431
|
-
):
|
|
604
|
+
): Effect.Effect<XurlTweetsResponse, unknown> {
|
|
432
605
|
if (ids.length === 0) {
|
|
433
|
-
return { data: [] };
|
|
606
|
+
return Effect.succeed({ data: [] });
|
|
434
607
|
}
|
|
435
608
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
609
|
+
return Effect.gen(function* () {
|
|
610
|
+
const tweets = yield* Effect.forEach(
|
|
611
|
+
ids,
|
|
612
|
+
(id) =>
|
|
613
|
+
Effect.gen(function* () {
|
|
614
|
+
const stdout = yield* runBirdJsonCommandEffect([
|
|
615
|
+
"read",
|
|
616
|
+
id,
|
|
617
|
+
"--json",
|
|
618
|
+
]);
|
|
619
|
+
const payload = yield* parseBirdJsonEffect(stdout);
|
|
620
|
+
return yield* normalizeBirdTweetItemEffect(payload, "read");
|
|
621
|
+
}),
|
|
622
|
+
{ concurrency: "unbounded" },
|
|
623
|
+
);
|
|
624
|
+
return normalizeBirdTweets(tweets);
|
|
625
|
+
});
|
|
626
|
+
}
|
|
442
627
|
|
|
443
|
-
|
|
628
|
+
export function lookupTweetsByIdsViaBird(
|
|
629
|
+
ids: string[],
|
|
630
|
+
): Promise<XurlTweetsResponse> {
|
|
631
|
+
return runEffectPromise(lookupTweetsByIdsViaBirdEffect(ids));
|
|
444
632
|
}
|
|
445
633
|
|
|
446
|
-
export
|
|
634
|
+
export function listHomeTimelineViaBirdEffect({
|
|
447
635
|
maxResults,
|
|
448
636
|
following = true,
|
|
449
637
|
}: {
|
|
450
638
|
maxResults: number;
|
|
451
639
|
following?: boolean;
|
|
452
|
-
}):
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
640
|
+
}): Effect.Effect<XurlMentionsResponse, unknown> {
|
|
641
|
+
return Effect.gen(function* () {
|
|
642
|
+
const args = ["home", "-n", String(maxResults), "--json"];
|
|
643
|
+
if (following) {
|
|
644
|
+
args.push("--following");
|
|
645
|
+
}
|
|
646
|
+
const stdout = yield* runBirdJsonCommandEffect(args);
|
|
647
|
+
const payload = yield* parseBirdJsonEffect(stdout);
|
|
648
|
+
return yield* normalizeBirdTweetsPayloadEffect(payload, "home");
|
|
649
|
+
});
|
|
650
|
+
}
|
|
459
651
|
|
|
460
|
-
|
|
652
|
+
export function listHomeTimelineViaBird(options: {
|
|
653
|
+
maxResults: number;
|
|
654
|
+
following?: boolean;
|
|
655
|
+
}): Promise<XurlMentionsResponse> {
|
|
656
|
+
return runEffectPromise(listHomeTimelineViaBirdEffect(options));
|
|
461
657
|
}
|
|
462
658
|
|
|
463
659
|
function normalizeBirdFollowUsers(
|
|
@@ -490,7 +686,18 @@ function normalizeBirdFollowUsers(
|
|
|
490
686
|
};
|
|
491
687
|
}
|
|
492
688
|
|
|
493
|
-
|
|
689
|
+
function normalizeBirdFollowUsersEffect(
|
|
690
|
+
payload: unknown,
|
|
691
|
+
command: "followers" | "following",
|
|
692
|
+
maxResults: number,
|
|
693
|
+
) {
|
|
694
|
+
return Effect.try({
|
|
695
|
+
try: () => normalizeBirdFollowUsers(payload, command, maxResults),
|
|
696
|
+
catch: (error) => error,
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export function listFollowUsersViaBirdEffect({
|
|
494
701
|
direction,
|
|
495
702
|
userId,
|
|
496
703
|
maxResults,
|
|
@@ -502,24 +709,39 @@ export async function listFollowUsersViaBird({
|
|
|
502
709
|
maxResults: number;
|
|
503
710
|
all?: boolean;
|
|
504
711
|
maxPages?: number;
|
|
505
|
-
}):
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
712
|
+
}): Effect.Effect<XurlFollowUsersResponse, unknown> {
|
|
713
|
+
return Effect.gen(function* () {
|
|
714
|
+
const args = [direction, "-n", String(maxResults), "--json"];
|
|
715
|
+
if (userId) {
|
|
716
|
+
args.push("--user", userId);
|
|
717
|
+
}
|
|
718
|
+
if (all) {
|
|
719
|
+
args.push("--all");
|
|
720
|
+
}
|
|
721
|
+
if (maxPages !== undefined) {
|
|
722
|
+
args.push("--max-pages", String(maxPages));
|
|
723
|
+
}
|
|
724
|
+
const stdout = yield* runBirdJsonCommandEffect(args);
|
|
725
|
+
const payload = yield* parseBirdJsonEffect(stdout);
|
|
726
|
+
return yield* normalizeBirdFollowUsersEffect(
|
|
727
|
+
payload,
|
|
728
|
+
direction,
|
|
729
|
+
maxResults,
|
|
730
|
+
);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
518
733
|
|
|
519
|
-
|
|
734
|
+
export function listFollowUsersViaBird(options: {
|
|
735
|
+
direction: "followers" | "following";
|
|
736
|
+
userId?: string;
|
|
737
|
+
maxResults: number;
|
|
738
|
+
all?: boolean;
|
|
739
|
+
maxPages?: number;
|
|
740
|
+
}): Promise<XurlFollowUsersResponse> {
|
|
741
|
+
return runEffectPromise(listFollowUsersViaBirdEffect(options));
|
|
520
742
|
}
|
|
521
743
|
|
|
522
|
-
export
|
|
744
|
+
export function listThreadViaBirdEffect({
|
|
523
745
|
tweetId,
|
|
524
746
|
all,
|
|
525
747
|
maxPages,
|
|
@@ -529,75 +751,224 @@ export async function listThreadViaBird({
|
|
|
529
751
|
all?: boolean;
|
|
530
752
|
maxPages?: number;
|
|
531
753
|
timeoutMs?: number;
|
|
754
|
+
}): Effect.Effect<XurlMentionsResponse, unknown> {
|
|
755
|
+
return Effect.gen(function* () {
|
|
756
|
+
const args = ["thread", tweetId, "--json"];
|
|
757
|
+
if (all) {
|
|
758
|
+
args.push("--all");
|
|
759
|
+
}
|
|
760
|
+
if (maxPages !== undefined) {
|
|
761
|
+
args.push("--max-pages", String(maxPages));
|
|
762
|
+
}
|
|
763
|
+
const stdout = yield* runBirdJsonCommandEffect(args, timeoutMs);
|
|
764
|
+
const payload = yield* parseBirdJsonEffect(stdout);
|
|
765
|
+
return yield* normalizeBirdTweetsPayloadEffect(payload, "thread");
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
export function listThreadViaBird(options: {
|
|
770
|
+
tweetId: string;
|
|
771
|
+
all?: boolean;
|
|
772
|
+
maxPages?: number;
|
|
773
|
+
timeoutMs?: number;
|
|
532
774
|
}): Promise<XurlMentionsResponse> {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
775
|
+
return runEffectPromise(listThreadViaBirdEffect(options));
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function normalizeBirdDmsPayloadEffect(payload: unknown) {
|
|
779
|
+
return Effect.try({
|
|
780
|
+
try: () => {
|
|
781
|
+
if (
|
|
782
|
+
!payload ||
|
|
783
|
+
typeof payload !== "object" ||
|
|
784
|
+
(payload as { success?: unknown }).success !== true ||
|
|
785
|
+
!Array.isArray(
|
|
786
|
+
(payload as { conversations?: unknown }).conversations,
|
|
787
|
+
) ||
|
|
788
|
+
!Array.isArray((payload as { events?: unknown }).events)
|
|
789
|
+
) {
|
|
790
|
+
throw new Error("bird dms returned unexpected JSON");
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return payload as BirdDmsResponse;
|
|
794
|
+
},
|
|
795
|
+
catch: (error) => error,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function parseBirdWhoami(stdout: string): BirdAuthenticatedAccount {
|
|
800
|
+
const usernameMatch = stdout.match(/@([A-Za-z0-9_]{1,15})\b/);
|
|
801
|
+
if (!usernameMatch?.[1]) {
|
|
802
|
+
throw new Error("bird whoami did not report an authenticated username");
|
|
539
803
|
}
|
|
540
|
-
const
|
|
541
|
-
|
|
804
|
+
const id = stdout
|
|
805
|
+
.split(/\r?\n/)
|
|
806
|
+
.map((line) => line.trim())
|
|
807
|
+
.map((line) => {
|
|
808
|
+
const labeled = line.match(/(?:🪪|user_?id:?)[^\d]*(\d{2,})/i);
|
|
809
|
+
if (labeled?.[1]) {
|
|
810
|
+
return labeled[1];
|
|
811
|
+
}
|
|
812
|
+
if (/[A-Za-z@]/.test(line)) {
|
|
813
|
+
return undefined;
|
|
814
|
+
}
|
|
815
|
+
return line.match(/^\D*(\d{2,})\D*$/)?.[1];
|
|
816
|
+
})
|
|
817
|
+
.find((value): value is string => Boolean(value));
|
|
818
|
+
return {
|
|
819
|
+
username: usernameMatch[1],
|
|
820
|
+
...(id ? { id } : {}),
|
|
821
|
+
};
|
|
822
|
+
}
|
|
542
823
|
|
|
543
|
-
|
|
824
|
+
export function getAuthenticatedBirdAccountEffect(): Effect.Effect<
|
|
825
|
+
BirdAuthenticatedAccount,
|
|
826
|
+
unknown
|
|
827
|
+
> {
|
|
828
|
+
return Effect.gen(function* () {
|
|
829
|
+
const stdout = yield* runBirdJsonCommandEffect(["whoami"]);
|
|
830
|
+
return yield* Effect.try({
|
|
831
|
+
try: () => parseBirdWhoami(stdout),
|
|
832
|
+
catch: (error) => error,
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
export function getAuthenticatedBirdAccount(): Promise<BirdAuthenticatedAccount> {
|
|
838
|
+
return runEffectPromise(getAuthenticatedBirdAccountEffect());
|
|
544
839
|
}
|
|
545
840
|
|
|
546
|
-
export
|
|
841
|
+
export function listDirectMessagesViaBirdEffect({
|
|
547
842
|
maxResults,
|
|
843
|
+
inbox = "all",
|
|
844
|
+
maxPages,
|
|
845
|
+
allPages = false,
|
|
846
|
+
pageDelayMs,
|
|
548
847
|
}: {
|
|
549
848
|
maxResults: number;
|
|
849
|
+
inbox?: "all" | "accepted" | "requests";
|
|
850
|
+
maxPages?: number;
|
|
851
|
+
allPages?: boolean;
|
|
852
|
+
pageDelayMs?: number;
|
|
853
|
+
}): Effect.Effect<BirdDmsResponse, unknown> {
|
|
854
|
+
return Effect.gen(function* () {
|
|
855
|
+
const args = ["dms", "-n", String(maxResults), "--json"];
|
|
856
|
+
if (inbox !== "all") {
|
|
857
|
+
args.push("--inbox", inbox);
|
|
858
|
+
}
|
|
859
|
+
if (allPages) {
|
|
860
|
+
args.push("--all-pages");
|
|
861
|
+
} else if (typeof maxPages === "number") {
|
|
862
|
+
args.push("--max-pages", String(maxPages));
|
|
863
|
+
}
|
|
864
|
+
if (typeof pageDelayMs === "number" && pageDelayMs > 0) {
|
|
865
|
+
args.push("--page-delay-ms", String(pageDelayMs));
|
|
866
|
+
}
|
|
867
|
+
const stdout = yield* runBirdJsonCommandEffect(args);
|
|
868
|
+
const payload = yield* parseBirdJsonEffect(stdout);
|
|
869
|
+
return yield* normalizeBirdDmsPayloadEffect(payload);
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
export function listDirectMessagesViaBird(options: {
|
|
874
|
+
maxResults: number;
|
|
875
|
+
inbox?: "all" | "accepted" | "requests";
|
|
876
|
+
maxPages?: number;
|
|
877
|
+
allPages?: boolean;
|
|
878
|
+
pageDelayMs?: number;
|
|
550
879
|
}): Promise<BirdDmsResponse> {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
"-n",
|
|
554
|
-
String(maxResults),
|
|
555
|
-
"--json",
|
|
556
|
-
]);
|
|
557
|
-
const payload = parseBirdJson(stdout);
|
|
558
|
-
if (
|
|
559
|
-
!payload ||
|
|
560
|
-
typeof payload !== "object" ||
|
|
561
|
-
(payload as { success?: unknown }).success !== true ||
|
|
562
|
-
!Array.isArray((payload as { conversations?: unknown }).conversations) ||
|
|
563
|
-
!Array.isArray((payload as { events?: unknown }).events)
|
|
564
|
-
) {
|
|
565
|
-
throw new Error("bird dms returned unexpected JSON");
|
|
566
|
-
}
|
|
880
|
+
return runEffectPromise(listDirectMessagesViaBirdEffect(options));
|
|
881
|
+
}
|
|
567
882
|
|
|
568
|
-
|
|
883
|
+
export function runDirectMessageRequestMutationViaBirdEffect({
|
|
884
|
+
action,
|
|
885
|
+
conversationId,
|
|
886
|
+
maxPages,
|
|
887
|
+
allPages = false,
|
|
888
|
+
}: {
|
|
889
|
+
action: BirdDmRequestAction;
|
|
890
|
+
conversationId: string;
|
|
891
|
+
maxPages?: number;
|
|
892
|
+
allPages?: boolean;
|
|
893
|
+
}): Effect.Effect<BirdDmMutationResponse, unknown> {
|
|
894
|
+
return Effect.gen(function* () {
|
|
895
|
+
const command =
|
|
896
|
+
action === "accept"
|
|
897
|
+
? "dm-accept"
|
|
898
|
+
: action === "reject"
|
|
899
|
+
? "dm-reject"
|
|
900
|
+
: "dm-block";
|
|
901
|
+
const args = [command, conversationId, "--json"];
|
|
902
|
+
if (action === "block") {
|
|
903
|
+
if (allPages) {
|
|
904
|
+
args.push("--all-pages");
|
|
905
|
+
} else if (typeof maxPages === "number") {
|
|
906
|
+
args.push("--max-pages", String(maxPages));
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
const stdout = yield* runBirdJsonCommandAllowFailureEffect(args);
|
|
910
|
+
const payload = yield* parseBirdJsonEffect(stdout);
|
|
911
|
+
if (
|
|
912
|
+
payload &&
|
|
913
|
+
typeof payload === "object" &&
|
|
914
|
+
typeof (payload as { success?: unknown }).success === "boolean"
|
|
915
|
+
) {
|
|
916
|
+
return payload as BirdDmMutationResponse;
|
|
917
|
+
}
|
|
918
|
+
throw new Error(`bird ${command} returned unexpected JSON`);
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
export function runDirectMessageRequestMutationViaBird(options: {
|
|
923
|
+
action: BirdDmRequestAction;
|
|
924
|
+
conversationId: string;
|
|
925
|
+
maxPages?: number;
|
|
926
|
+
allPages?: boolean;
|
|
927
|
+
}): Promise<BirdDmMutationResponse> {
|
|
928
|
+
return runEffectPromise(
|
|
929
|
+
runDirectMessageRequestMutationViaBirdEffect(options),
|
|
930
|
+
);
|
|
569
931
|
}
|
|
570
932
|
|
|
571
|
-
export
|
|
933
|
+
export function lookupProfileViaBirdEffect(
|
|
572
934
|
usernameOrId: string,
|
|
573
|
-
):
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
935
|
+
): Effect.Effect<XurlMentionUser | null, unknown> {
|
|
936
|
+
return Effect.gen(function* () {
|
|
937
|
+
const target = usernameOrId.trim().replace(/^@/, "");
|
|
938
|
+
if (!target) {
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
578
941
|
|
|
579
|
-
|
|
580
|
-
try {
|
|
581
|
-
stdout = await runBirdJsonCommand([
|
|
942
|
+
const stdout = yield* runBirdJsonCommandEffect([
|
|
582
943
|
"user",
|
|
583
944
|
target,
|
|
584
945
|
"--json",
|
|
585
946
|
"--profile-only",
|
|
586
|
-
])
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
947
|
+
]).pipe(
|
|
948
|
+
Effect.catchAll((error) => {
|
|
949
|
+
if (!isUnsupportedBirdOptionError(error, "--profile-only")) {
|
|
950
|
+
return Effect.fail(error);
|
|
951
|
+
}
|
|
952
|
+
return runBirdJsonCommandEffect([
|
|
953
|
+
"user",
|
|
954
|
+
target,
|
|
955
|
+
"--json",
|
|
956
|
+
"--count",
|
|
957
|
+
"1",
|
|
958
|
+
]);
|
|
959
|
+
}),
|
|
960
|
+
);
|
|
961
|
+
const payload = (yield* parseBirdJsonEffect(
|
|
962
|
+
stdout,
|
|
963
|
+
)) as BirdUserOverviewPayload;
|
|
964
|
+
return toXurlMentionUser(payload.user);
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
export function lookupProfileViaBird(
|
|
969
|
+
usernameOrId: string,
|
|
970
|
+
): Promise<XurlMentionUser | null> {
|
|
971
|
+
return runEffectPromise(lookupProfileViaBirdEffect(usernameOrId));
|
|
601
972
|
}
|
|
602
973
|
|
|
603
974
|
function toXurlMentionUser(
|
|
@@ -642,10 +1013,11 @@ function toXurlMentionUser(
|
|
|
642
1013
|
};
|
|
643
1014
|
}
|
|
644
1015
|
|
|
645
|
-
export
|
|
1016
|
+
export function lookupProfilesViaBirdEffect(
|
|
646
1017
|
usernameOrIds: string[],
|
|
647
|
-
):
|
|
648
|
-
Array<{ target: string; user: XurlMentionUser | null; error?: string }
|
|
1018
|
+
): Effect.Effect<
|
|
1019
|
+
Array<{ target: string; user: XurlMentionUser | null; error?: string }>,
|
|
1020
|
+
unknown
|
|
649
1021
|
> {
|
|
650
1022
|
const targets = Array.from(
|
|
651
1023
|
new Set(
|
|
@@ -655,56 +1027,72 @@ export async function lookupProfilesViaBird(
|
|
|
655
1027
|
),
|
|
656
1028
|
);
|
|
657
1029
|
if (targets.length === 0) {
|
|
658
|
-
return [];
|
|
1030
|
+
return Effect.succeed([]);
|
|
659
1031
|
}
|
|
660
1032
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
String(item.target ?? "")
|
|
675
|
-
.replace(/^@/, "")
|
|
676
|
-
.toLowerCase(),
|
|
677
|
-
item.error ?? "Unknown error",
|
|
678
|
-
]),
|
|
679
|
-
);
|
|
680
|
-
return targets.map((target) => ({
|
|
681
|
-
target,
|
|
682
|
-
user: byTarget.get(target.toLowerCase()) ?? null,
|
|
683
|
-
...(errors.has(target.toLowerCase())
|
|
684
|
-
? { error: errors.get(target.toLowerCase()) }
|
|
685
|
-
: {}),
|
|
686
|
-
}));
|
|
687
|
-
} catch (error) {
|
|
688
|
-
if (!isUnsupportedBirdOptionError(error, "profiles")) {
|
|
689
|
-
throw error;
|
|
690
|
-
}
|
|
691
|
-
return Promise.all(
|
|
692
|
-
targets.map(async (target) => {
|
|
693
|
-
try {
|
|
694
|
-
return { target, user: await lookupProfileViaBird(target) };
|
|
695
|
-
} catch (lookupError) {
|
|
696
|
-
return {
|
|
697
|
-
target,
|
|
698
|
-
user: null,
|
|
699
|
-
error:
|
|
700
|
-
lookupError instanceof Error
|
|
701
|
-
? lookupError.message
|
|
702
|
-
: String(lookupError),
|
|
703
|
-
};
|
|
1033
|
+
return runBirdJsonCommandEffect(["profiles", ...targets, "--json"]).pipe(
|
|
1034
|
+
Effect.flatMap((stdout) =>
|
|
1035
|
+
Effect.gen(function* () {
|
|
1036
|
+
const payload = (yield* parseBirdJsonEffect(
|
|
1037
|
+
stdout,
|
|
1038
|
+
)) as BirdProfilesPayload;
|
|
1039
|
+
const users = (payload.users ?? [])
|
|
1040
|
+
.map(toXurlMentionUser)
|
|
1041
|
+
.filter((user): user is XurlMentionUser => Boolean(user));
|
|
1042
|
+
const byTarget = new Map<string, XurlMentionUser>();
|
|
1043
|
+
for (const user of users) {
|
|
1044
|
+
byTarget.set(String(user.id), user);
|
|
1045
|
+
byTarget.set(user.username.toLowerCase(), user);
|
|
704
1046
|
}
|
|
1047
|
+
const errors = new Map(
|
|
1048
|
+
(payload.errors ?? []).map((item) => [
|
|
1049
|
+
String(item.target ?? "")
|
|
1050
|
+
.replace(/^@/, "")
|
|
1051
|
+
.toLowerCase(),
|
|
1052
|
+
item.error ?? "Unknown error",
|
|
1053
|
+
]),
|
|
1054
|
+
);
|
|
1055
|
+
return targets.map((target) => ({
|
|
1056
|
+
target,
|
|
1057
|
+
user: byTarget.get(target.toLowerCase()) ?? null,
|
|
1058
|
+
...(errors.has(target.toLowerCase())
|
|
1059
|
+
? { error: errors.get(target.toLowerCase()) }
|
|
1060
|
+
: {}),
|
|
1061
|
+
}));
|
|
705
1062
|
}),
|
|
706
|
-
)
|
|
707
|
-
|
|
1063
|
+
),
|
|
1064
|
+
Effect.catchAll((error) => {
|
|
1065
|
+
if (!isUnsupportedBirdOptionError(error, "profiles")) {
|
|
1066
|
+
return Effect.fail(error);
|
|
1067
|
+
}
|
|
1068
|
+
return Effect.forEach(
|
|
1069
|
+
targets,
|
|
1070
|
+
(target) =>
|
|
1071
|
+
lookupProfileViaBirdEffect(target).pipe(
|
|
1072
|
+
Effect.map((user) => ({ target, user })),
|
|
1073
|
+
Effect.catchAll((lookupError) =>
|
|
1074
|
+
Effect.succeed({
|
|
1075
|
+
target,
|
|
1076
|
+
user: null,
|
|
1077
|
+
error:
|
|
1078
|
+
lookupError instanceof Error
|
|
1079
|
+
? lookupError.message
|
|
1080
|
+
: String(lookupError),
|
|
1081
|
+
}),
|
|
1082
|
+
),
|
|
1083
|
+
),
|
|
1084
|
+
{ concurrency: "unbounded" },
|
|
1085
|
+
);
|
|
1086
|
+
}),
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
export function lookupProfilesViaBird(
|
|
1091
|
+
usernameOrIds: string[],
|
|
1092
|
+
): Promise<
|
|
1093
|
+
Array<{ target: string; user: XurlMentionUser | null; error?: string }>
|
|
1094
|
+
> {
|
|
1095
|
+
return runEffectPromise(lookupProfilesViaBirdEffect(usernameOrIds));
|
|
708
1096
|
}
|
|
709
1097
|
|
|
710
1098
|
export const __test__ = {
|