birdclaw 0.6.0 → 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 +48 -0
- package/README.md +25 -0
- package/package.json +6 -1
- package/src/cli.ts +438 -26
- package/src/components/AppNav.tsx +10 -0
- package/src/components/MarkdownViewer.tsx +438 -72
- 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 +5 -2
- package/src/components/TimelineCard.tsx +20 -4
- package/src/components/TimelineRouteFrame.tsx +16 -0
- package/src/components/TweetRichText.tsx +36 -12
- package/src/components/useTimelineRouteData.ts +74 -6
- package/src/lib/account-sync-job.ts +15 -3
- package/src/lib/archive-finder.ts +1 -1
- package/src/lib/archive-import.ts +245 -7
- package/src/lib/authored-live.ts +1 -0
- package/src/lib/avatar-cache.ts +50 -0
- package/src/lib/backup.ts +4 -3
- package/src/lib/bird.ts +33 -0
- package/src/lib/config.ts +35 -2
- package/src/lib/data-sources.ts +219 -0
- package/src/lib/db.ts +62 -1
- package/src/lib/geocoding.ts +296 -0
- package/src/lib/location.ts +137 -0
- package/src/lib/mention-threads-live.ts +94 -1
- package/src/lib/mentions-live.ts +187 -40
- package/src/lib/network-map.ts +382 -0
- package/src/lib/period-digest.ts +377 -13
- package/src/lib/profile-analysis.ts +1272 -0
- package/src/lib/profile-bio-entities.ts +1 -1
- package/src/lib/queries.ts +14 -4
- package/src/lib/search-discussion.ts +1016 -0
- package/src/lib/timeline-live.ts +272 -19
- package/src/lib/tweet-account-edges.ts +2 -0
- package/src/lib/tweet-render.ts +141 -1
- package/src/lib/tweet-search-live.ts +565 -0
- package/src/lib/types.ts +37 -2
- package/src/lib/ui.ts +1 -1
- package/src/lib/web-sync.ts +7 -2
- package/src/lib/xurl-rate-limits.ts +267 -0
- package/src/lib/xurl.ts +551 -41
- package/src/routeTree.gen.ts +231 -0
- package/src/routes/__root.tsx +5 -6
- package/src/routes/api/data-sources.tsx +24 -0
- package/src/routes/api/network-map.tsx +55 -0
- package/src/routes/api/period-digest.tsx +11 -1
- package/src/routes/api/profile-analysis.tsx +152 -0
- package/src/routes/api/query.tsx +1 -0
- package/src/routes/api/search-discussion.tsx +169 -0
- 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/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 +22 -8
- package/src/styles.css +22 -0
package/src/lib/mentions-live.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { Database } from "./sqlite";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getAuthenticatedBirdAccountEffect,
|
|
5
|
+
listMentionsViaBirdEffect,
|
|
6
|
+
} from "./bird";
|
|
4
7
|
import type { MentionsDataSource } from "./config";
|
|
5
8
|
import { getNativeDb } from "./db";
|
|
6
9
|
import { runEffectPromise, tryPromise } from "./effect-runtime";
|
|
@@ -24,6 +27,16 @@ export const DEFAULT_MENTIONS_CACHE_TTL_MS = 2 * 60_000;
|
|
|
24
27
|
const MIN_XURL_MENTIONS_LIMIT = 5;
|
|
25
28
|
const MAX_XURL_MENTIONS_LIMIT = 100;
|
|
26
29
|
type MentionSyncMode = Exclude<MentionsDataSource, "birdclaw">;
|
|
30
|
+
type MentionLiveSource = Exclude<MentionSyncMode, "auto">;
|
|
31
|
+
export interface MentionsProgress {
|
|
32
|
+
source: "bird" | "xurl" | "cache";
|
|
33
|
+
fetched: number;
|
|
34
|
+
total?: number;
|
|
35
|
+
page?: number;
|
|
36
|
+
maxPages?: number;
|
|
37
|
+
pageSize?: number;
|
|
38
|
+
done: boolean;
|
|
39
|
+
}
|
|
27
40
|
export interface SyncMentionsOptions {
|
|
28
41
|
account?: string;
|
|
29
42
|
mode?: string;
|
|
@@ -33,9 +46,10 @@ export interface SyncMentionsOptions {
|
|
|
33
46
|
cacheTtlMs?: number;
|
|
34
47
|
sinceId?: string;
|
|
35
48
|
startTime?: string;
|
|
49
|
+
onProgress?: (progress: MentionsProgress) => void;
|
|
36
50
|
}
|
|
37
51
|
interface ExportMentionsViaCachedLiveSourceOptions {
|
|
38
|
-
mode:
|
|
52
|
+
mode: MentionSyncMode;
|
|
39
53
|
account?: string;
|
|
40
54
|
search?: string;
|
|
41
55
|
replyFilter?: ReplyFilter;
|
|
@@ -52,7 +66,7 @@ type MentionScanBoundary =
|
|
|
52
66
|
| { kind: "unbounded" };
|
|
53
67
|
interface MentionScanShape {
|
|
54
68
|
endpoint: "mentions";
|
|
55
|
-
mode:
|
|
69
|
+
mode: MentionLiveSource;
|
|
56
70
|
accountId: string;
|
|
57
71
|
pageSize: number;
|
|
58
72
|
boundary: MentionScanBoundary;
|
|
@@ -167,7 +181,7 @@ function getMentionHighWaterKey({
|
|
|
167
181
|
mode,
|
|
168
182
|
accountId,
|
|
169
183
|
}: {
|
|
170
|
-
mode:
|
|
184
|
+
mode: MentionLiveSource;
|
|
171
185
|
accountId: string;
|
|
172
186
|
}) {
|
|
173
187
|
return `mentions:sync:high-water:v1:mode=${mode}:account=${encodeCacheKeyPart(accountId)}`;
|
|
@@ -268,9 +282,9 @@ function parseMaxPages(value?: number) {
|
|
|
268
282
|
}
|
|
269
283
|
|
|
270
284
|
function parseSyncMode(value?: string): MentionSyncMode {
|
|
271
|
-
const mode = value ?? "
|
|
272
|
-
if (mode !== "bird" && mode !== "xurl") {
|
|
273
|
-
throw new Error("--mode must be bird or xurl");
|
|
285
|
+
const mode = value ?? "auto";
|
|
286
|
+
if (mode !== "auto" && mode !== "bird" && mode !== "xurl") {
|
|
287
|
+
throw new Error("--mode must be auto, bird, or xurl");
|
|
274
288
|
}
|
|
275
289
|
return mode;
|
|
276
290
|
}
|
|
@@ -392,7 +406,7 @@ function getNewestMentionId(payload: XurlMentionsResponse) {
|
|
|
392
406
|
|
|
393
407
|
function readMentionHighWaterId(
|
|
394
408
|
db: Database,
|
|
395
|
-
mode:
|
|
409
|
+
mode: MentionLiveSource,
|
|
396
410
|
accountId: string,
|
|
397
411
|
) {
|
|
398
412
|
const cached = readSyncCache<MentionHighWaterValue>(
|
|
@@ -406,7 +420,7 @@ function readMentionHighWaterId(
|
|
|
406
420
|
|
|
407
421
|
function writeMentionHighWaterId(
|
|
408
422
|
db: Database,
|
|
409
|
-
mode:
|
|
423
|
+
mode: MentionLiveSource,
|
|
410
424
|
accountId: string,
|
|
411
425
|
sinceId: string | undefined,
|
|
412
426
|
) {
|
|
@@ -419,18 +433,24 @@ function writeMentionHighWaterId(
|
|
|
419
433
|
function resolveAccount(db: Database, accountId?: string) {
|
|
420
434
|
const row = accountId
|
|
421
435
|
? (db
|
|
422
|
-
.prepare(
|
|
423
|
-
|
|
436
|
+
.prepare(
|
|
437
|
+
"select id, handle, external_user_id from accounts where id = ?",
|
|
438
|
+
)
|
|
439
|
+
.get(accountId) as
|
|
440
|
+
| { id: string; handle: string; external_user_id: string | null }
|
|
441
|
+
| undefined)
|
|
424
442
|
: (db
|
|
425
443
|
.prepare(
|
|
426
444
|
`
|
|
427
|
-
select id, handle
|
|
445
|
+
select id, handle, external_user_id
|
|
428
446
|
from accounts
|
|
429
447
|
order by is_default desc, created_at asc
|
|
430
448
|
limit 1
|
|
431
449
|
`,
|
|
432
450
|
)
|
|
433
|
-
.get() as
|
|
451
|
+
.get() as
|
|
452
|
+
| { id: string; handle: string; external_user_id: string | null }
|
|
453
|
+
| undefined);
|
|
434
454
|
|
|
435
455
|
if (!row) {
|
|
436
456
|
throw new Error(`Unknown account: ${accountId ?? "default"}`);
|
|
@@ -439,9 +459,45 @@ function resolveAccount(db: Database, accountId?: string) {
|
|
|
439
459
|
return {
|
|
440
460
|
accountId: row.id,
|
|
441
461
|
username: row.handle.replace(/^@/, ""),
|
|
462
|
+
externalUserId:
|
|
463
|
+
typeof row.external_user_id === "string" &&
|
|
464
|
+
row.external_user_id.trim().length > 0
|
|
465
|
+
? row.external_user_id.trim()
|
|
466
|
+
: undefined,
|
|
442
467
|
};
|
|
443
468
|
}
|
|
444
469
|
|
|
470
|
+
function verifyBirdAccountMatchesEffect({
|
|
471
|
+
accountId,
|
|
472
|
+
username,
|
|
473
|
+
externalUserId,
|
|
474
|
+
}: ReturnType<typeof resolveAccount>) {
|
|
475
|
+
return Effect.gen(function* () {
|
|
476
|
+
const authenticated = yield* getAuthenticatedBirdAccountEffect();
|
|
477
|
+
if (
|
|
478
|
+
externalUserId &&
|
|
479
|
+
authenticated.id &&
|
|
480
|
+
authenticated.id === externalUserId
|
|
481
|
+
) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (externalUserId && authenticated.id) {
|
|
485
|
+
return yield* Effect.fail(
|
|
486
|
+
new Error(
|
|
487
|
+
`bird is authenticated as user ${authenticated.id}; refusing to sync into ${accountId} (${externalUserId})`,
|
|
488
|
+
),
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
if (authenticated.username.toLowerCase() !== username.toLowerCase()) {
|
|
492
|
+
return yield* Effect.fail(
|
|
493
|
+
new Error(
|
|
494
|
+
`bird is authenticated as @${authenticated.username}; refusing to sync into ${accountId} (@${username})`,
|
|
495
|
+
),
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
445
501
|
function findNewestArchiveMentionId(db: Database, accountId: string) {
|
|
446
502
|
const row = db
|
|
447
503
|
.prepare(
|
|
@@ -697,6 +753,7 @@ function fetchMentionsViaXurlEffect({
|
|
|
697
753
|
sinceId,
|
|
698
754
|
startPaginationToken,
|
|
699
755
|
startTime,
|
|
756
|
+
onProgress,
|
|
700
757
|
}: {
|
|
701
758
|
resolvedAccount: ReturnType<typeof resolveAccount>;
|
|
702
759
|
limit: number;
|
|
@@ -705,12 +762,15 @@ function fetchMentionsViaXurlEffect({
|
|
|
705
762
|
sinceId?: string;
|
|
706
763
|
startPaginationToken?: string;
|
|
707
764
|
startTime?: string;
|
|
765
|
+
onProgress?: (progress: MentionsProgress) => void;
|
|
708
766
|
}) {
|
|
709
767
|
return Effect.gen(function* () {
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
768
|
+
const accountUserId =
|
|
769
|
+
resolvedAccount.externalUserId ??
|
|
770
|
+
(yield* tryPromise(() =>
|
|
771
|
+
lookupUsersByHandles([resolvedAccount.username]),
|
|
772
|
+
).pipe(Effect.map((users) => users[0]?.id)));
|
|
773
|
+
if (!accountUserId) {
|
|
714
774
|
return yield* Effect.fail(
|
|
715
775
|
new Error(
|
|
716
776
|
`Could not resolve Twitter user id for @${resolvedAccount.username}`,
|
|
@@ -726,7 +786,7 @@ function fetchMentionsViaXurlEffect({
|
|
|
726
786
|
listMentionsViaXurl({
|
|
727
787
|
maxResults: limit,
|
|
728
788
|
username: resolvedAccount.username,
|
|
729
|
-
userId: String(
|
|
789
|
+
userId: String(accountUserId),
|
|
730
790
|
paginationToken: nextToken,
|
|
731
791
|
...(sinceId ? { sinceId } : {}),
|
|
732
792
|
...(startTime ? { startTime } : {}),
|
|
@@ -739,6 +799,22 @@ function fetchMentionsViaXurlEffect({
|
|
|
739
799
|
: undefined;
|
|
740
800
|
nextToken = metaNextToken;
|
|
741
801
|
pageCount += 1;
|
|
802
|
+
const fetched = pages.reduce((sum, item) => sum + item.data.length, 0);
|
|
803
|
+
const done =
|
|
804
|
+
!all ||
|
|
805
|
+
!nextToken ||
|
|
806
|
+
(parsedMaxPages !== null && pageCount >= parsedMaxPages);
|
|
807
|
+
yield* Effect.sync(() =>
|
|
808
|
+
onProgress?.({
|
|
809
|
+
source: "xurl",
|
|
810
|
+
fetched,
|
|
811
|
+
total: parsedMaxPages === null ? undefined : parsedMaxPages * limit,
|
|
812
|
+
page: pageCount,
|
|
813
|
+
maxPages: parsedMaxPages ?? undefined,
|
|
814
|
+
pageSize: limit,
|
|
815
|
+
done,
|
|
816
|
+
}),
|
|
817
|
+
);
|
|
742
818
|
} while (
|
|
743
819
|
all &&
|
|
744
820
|
nextToken &&
|
|
@@ -776,28 +852,34 @@ export function syncMentionsEffect({
|
|
|
776
852
|
cacheTtlMs,
|
|
777
853
|
sinceId,
|
|
778
854
|
startTime,
|
|
855
|
+
onProgress,
|
|
779
856
|
}: SyncMentionsOptions) {
|
|
780
857
|
return Effect.gen(function* () {
|
|
781
858
|
const parsedMode = yield* trySync(() => parseSyncMode(mode));
|
|
859
|
+
const primaryMode: MentionLiveSource =
|
|
860
|
+
parsedMode === "auto" ? "xurl" : parsedMode;
|
|
782
861
|
const explicitSinceId = sinceId?.trim() || undefined;
|
|
783
862
|
const explicitStartTime = startTime?.trim() || undefined;
|
|
784
|
-
if (
|
|
863
|
+
if (primaryMode === "bird" && (explicitSinceId || explicitStartTime)) {
|
|
785
864
|
return yield* Effect.fail(
|
|
786
865
|
new Error("bird mode does not support --since-id or --start-time"),
|
|
787
866
|
);
|
|
788
867
|
}
|
|
789
|
-
if (
|
|
868
|
+
if (primaryMode === "xurl") {
|
|
790
869
|
yield* trySync(() => assertXurlLimit(limit));
|
|
791
870
|
} else {
|
|
792
871
|
yield* trySync(() => assertBirdLimit(limit));
|
|
793
872
|
}
|
|
794
873
|
const parsedMaxPages = yield* trySync(() => parseMaxPages(maxPages));
|
|
795
|
-
const fetchAll =
|
|
874
|
+
const fetchAll =
|
|
875
|
+
primaryMode === "xurl" &&
|
|
876
|
+
(parsedMaxPages !== null ||
|
|
877
|
+
Boolean(explicitSinceId || explicitStartTime));
|
|
796
878
|
const db = yield* trySync(() => getNativeDb());
|
|
797
879
|
const resolvedAccount = yield* trySync(() => resolveAccount(db, account));
|
|
798
880
|
const cursorShape: MentionScanShape = {
|
|
799
881
|
endpoint: "mentions",
|
|
800
|
-
mode:
|
|
882
|
+
mode: primaryMode,
|
|
801
883
|
accountId: resolvedAccount.accountId,
|
|
802
884
|
pageSize: limit,
|
|
803
885
|
boundary: getMentionCursorBoundary({
|
|
@@ -808,7 +890,7 @@ export function syncMentionsEffect({
|
|
|
808
890
|
const cursorKey = getMentionCursorKey(cursorShape);
|
|
809
891
|
const legacyCursorKeys = getLegacyMentionCursorKeys(cursorShape);
|
|
810
892
|
const cursor =
|
|
811
|
-
|
|
893
|
+
primaryMode === "xurl"
|
|
812
894
|
? yield* trySync(() =>
|
|
813
895
|
readMentionCursor({
|
|
814
896
|
db,
|
|
@@ -826,15 +908,15 @@ export function syncMentionsEffect({
|
|
|
826
908
|
? cursor.boundary.startTime
|
|
827
909
|
: undefined;
|
|
828
910
|
const committedSinceId =
|
|
829
|
-
|
|
911
|
+
primaryMode === "xurl" &&
|
|
830
912
|
cursorShape.boundary.kind === "auto" &&
|
|
831
913
|
!startPaginationToken
|
|
832
914
|
? yield* trySync(() =>
|
|
833
|
-
readMentionHighWaterId(db,
|
|
915
|
+
readMentionHighWaterId(db, primaryMode, resolvedAccount.accountId),
|
|
834
916
|
)
|
|
835
917
|
: undefined;
|
|
836
918
|
const seededSinceId =
|
|
837
|
-
|
|
919
|
+
primaryMode === "xurl" &&
|
|
838
920
|
!explicitSinceId &&
|
|
839
921
|
!explicitStartTime &&
|
|
840
922
|
!startPaginationToken
|
|
@@ -853,7 +935,7 @@ export function syncMentionsEffect({
|
|
|
853
935
|
: undefined;
|
|
854
936
|
const resultShape: MentionScanShape = {
|
|
855
937
|
endpoint: "mentions",
|
|
856
|
-
mode:
|
|
938
|
+
mode: primaryMode,
|
|
857
939
|
accountId: resolvedAccount.accountId,
|
|
858
940
|
pageSize: limit,
|
|
859
941
|
boundary: getMentionRequestBoundary({
|
|
@@ -889,9 +971,17 @@ export function syncMentionsEffect({
|
|
|
889
971
|
db,
|
|
890
972
|
resolvedAccount.accountId,
|
|
891
973
|
cached.value,
|
|
892
|
-
|
|
974
|
+
primaryMode,
|
|
893
975
|
),
|
|
894
976
|
);
|
|
977
|
+
yield* Effect.sync(() =>
|
|
978
|
+
onProgress?.({
|
|
979
|
+
source: "cache",
|
|
980
|
+
fetched: cached.value.data.length,
|
|
981
|
+
total: parsedMaxPages === null ? undefined : parsedMaxPages * limit,
|
|
982
|
+
done: true,
|
|
983
|
+
}),
|
|
984
|
+
);
|
|
895
985
|
return {
|
|
896
986
|
ok: true,
|
|
897
987
|
source: "cache",
|
|
@@ -907,7 +997,7 @@ export function syncMentionsEffect({
|
|
|
907
997
|
}
|
|
908
998
|
|
|
909
999
|
if (
|
|
910
|
-
|
|
1000
|
+
primaryMode === "xurl" &&
|
|
911
1001
|
!explicitSinceId &&
|
|
912
1002
|
!explicitStartTime &&
|
|
913
1003
|
!startPaginationToken &&
|
|
@@ -918,8 +1008,15 @@ export function syncMentionsEffect({
|
|
|
918
1008
|
);
|
|
919
1009
|
}
|
|
920
1010
|
|
|
1011
|
+
let source: MentionLiveSource = primaryMode;
|
|
1012
|
+
const canFallbackToBird =
|
|
1013
|
+
primaryMode === "xurl" &&
|
|
1014
|
+
parsedMode === "auto" &&
|
|
1015
|
+
!explicitSinceId &&
|
|
1016
|
+
!explicitStartTime &&
|
|
1017
|
+
!startPaginationToken;
|
|
921
1018
|
const payload =
|
|
922
|
-
|
|
1019
|
+
primaryMode === "bird"
|
|
923
1020
|
? yield* fetchMentionsViaBirdEffect({ limit })
|
|
924
1021
|
: yield* fetchMentionsViaXurlEffect({
|
|
925
1022
|
resolvedAccount,
|
|
@@ -929,17 +1026,36 @@ export function syncMentionsEffect({
|
|
|
929
1026
|
sinceId: resolvedSinceId,
|
|
930
1027
|
startPaginationToken,
|
|
931
1028
|
startTime: resolvedStartTime,
|
|
932
|
-
|
|
1029
|
+
onProgress,
|
|
1030
|
+
}).pipe(
|
|
1031
|
+
Effect.catchAll((error) => {
|
|
1032
|
+
if (!canFallbackToBird) return Effect.fail(error);
|
|
1033
|
+
source = "bird";
|
|
1034
|
+
return verifyBirdAccountMatchesEffect(resolvedAccount).pipe(
|
|
1035
|
+
Effect.flatMap(() => fetchMentionsViaBirdEffect({ limit })),
|
|
1036
|
+
);
|
|
1037
|
+
}),
|
|
1038
|
+
);
|
|
1039
|
+
if (source === "bird") {
|
|
1040
|
+
yield* Effect.sync(() =>
|
|
1041
|
+
onProgress?.({
|
|
1042
|
+
source: "bird",
|
|
1043
|
+
fetched: payload.data.length,
|
|
1044
|
+
total: limit,
|
|
1045
|
+
done: true,
|
|
1046
|
+
}),
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
933
1049
|
yield* trySync(() =>
|
|
934
1050
|
mergeMentionsIntoLocalStore(
|
|
935
1051
|
db,
|
|
936
1052
|
resolvedAccount.accountId,
|
|
937
1053
|
payload,
|
|
938
|
-
|
|
1054
|
+
source,
|
|
939
1055
|
),
|
|
940
1056
|
);
|
|
941
1057
|
const payloadPaginationToken = getCachedPaginationToken({ value: payload });
|
|
942
|
-
if (
|
|
1058
|
+
if (source === "xurl") {
|
|
943
1059
|
if (payloadPaginationToken) {
|
|
944
1060
|
yield* trySync(() => {
|
|
945
1061
|
writeSyncCache(
|
|
@@ -976,7 +1092,7 @@ export function syncMentionsEffect({
|
|
|
976
1092
|
if (cursorShape.boundary.kind === "auto") {
|
|
977
1093
|
writeMentionHighWaterId(
|
|
978
1094
|
db,
|
|
979
|
-
|
|
1095
|
+
source,
|
|
980
1096
|
resolvedAccount.accountId,
|
|
981
1097
|
maxNumericTweetId(
|
|
982
1098
|
resolvedSinceId,
|
|
@@ -989,12 +1105,23 @@ export function syncMentionsEffect({
|
|
|
989
1105
|
}
|
|
990
1106
|
}
|
|
991
1107
|
if (!payloadPaginationToken && !startPaginationToken) {
|
|
992
|
-
|
|
1108
|
+
const writeCacheKey =
|
|
1109
|
+
source === primaryMode
|
|
1110
|
+
? resultCacheKey
|
|
1111
|
+
: getMentionResultCacheKey({
|
|
1112
|
+
shape: {
|
|
1113
|
+
...resultShape,
|
|
1114
|
+
mode: source,
|
|
1115
|
+
},
|
|
1116
|
+
all: false,
|
|
1117
|
+
maxPages: null,
|
|
1118
|
+
});
|
|
1119
|
+
yield* trySync(() => writeSyncCache(writeCacheKey, payload, db));
|
|
993
1120
|
}
|
|
994
1121
|
|
|
995
1122
|
return {
|
|
996
1123
|
ok: true,
|
|
997
|
-
source
|
|
1124
|
+
source,
|
|
998
1125
|
kind: "mentions",
|
|
999
1126
|
accountId: resolvedAccount.accountId,
|
|
1000
1127
|
count: payload.data.length,
|
|
@@ -1020,13 +1147,14 @@ function exportMentionsViaCachedLiveSourceEffect({
|
|
|
1020
1147
|
cacheTtlMs,
|
|
1021
1148
|
}: ExportMentionsViaCachedLiveSourceOptions) {
|
|
1022
1149
|
return Effect.gen(function* () {
|
|
1023
|
-
|
|
1150
|
+
const primaryMode: MentionLiveSource = mode === "auto" ? "xurl" : mode;
|
|
1151
|
+
if (primaryMode === "xurl") {
|
|
1024
1152
|
yield* trySync(() => assertXurlLimit(limit));
|
|
1025
1153
|
} else {
|
|
1026
1154
|
yield* trySync(() => assertBirdLimit(limit));
|
|
1027
1155
|
}
|
|
1028
1156
|
const parsedMaxPages = yield* trySync(() => parseMaxPages(maxPages));
|
|
1029
|
-
const fetchAll =
|
|
1157
|
+
const fetchAll = primaryMode === "xurl" && (all || parsedMaxPages !== null);
|
|
1030
1158
|
|
|
1031
1159
|
const db = yield* trySync(() => getNativeDb());
|
|
1032
1160
|
const resolvedAccount = yield* trySync(() => resolveAccount(db, account));
|
|
@@ -1068,8 +1196,9 @@ function exportMentionsViaCachedLiveSourceEffect({
|
|
|
1068
1196
|
return yield* trySync(() => readFilteredOrRaw(cached.value));
|
|
1069
1197
|
}
|
|
1070
1198
|
|
|
1199
|
+
let source: MentionLiveSource = primaryMode;
|
|
1071
1200
|
const liveResult = yield* (
|
|
1072
|
-
|
|
1201
|
+
primaryMode === "bird"
|
|
1073
1202
|
? fetchMentionsViaBirdEffect({ limit })
|
|
1074
1203
|
: fetchMentionsViaXurlEffect({
|
|
1075
1204
|
resolvedAccount,
|
|
@@ -1078,13 +1207,20 @@ function exportMentionsViaCachedLiveSourceEffect({
|
|
|
1078
1207
|
parsedMaxPages,
|
|
1079
1208
|
})
|
|
1080
1209
|
).pipe(
|
|
1210
|
+
Effect.catchAll((error) => {
|
|
1211
|
+
if (mode !== "auto" || fetchAll) return Effect.fail(error);
|
|
1212
|
+
source = "bird";
|
|
1213
|
+
return verifyBirdAccountMatchesEffect(resolvedAccount).pipe(
|
|
1214
|
+
Effect.flatMap(() => fetchMentionsViaBirdEffect({ limit })),
|
|
1215
|
+
);
|
|
1216
|
+
}),
|
|
1081
1217
|
Effect.flatMap((payload) =>
|
|
1082
1218
|
trySync(() => {
|
|
1083
1219
|
mergeMentionsIntoLocalStore(
|
|
1084
1220
|
db,
|
|
1085
1221
|
resolvedAccount.accountId,
|
|
1086
1222
|
payload,
|
|
1087
|
-
|
|
1223
|
+
source,
|
|
1088
1224
|
);
|
|
1089
1225
|
writeSyncCache(cacheKey, payload, db);
|
|
1090
1226
|
return readFilteredOrRaw(payload);
|
|
@@ -1133,3 +1269,14 @@ export function exportMentionsViaCachedBird(
|
|
|
1133
1269
|
}),
|
|
1134
1270
|
);
|
|
1135
1271
|
}
|
|
1272
|
+
|
|
1273
|
+
export function exportMentionsViaCachedAuto(
|
|
1274
|
+
options: Omit<ExportMentionsViaCachedLiveSourceOptions, "mode">,
|
|
1275
|
+
) {
|
|
1276
|
+
return runEffectPromise(
|
|
1277
|
+
exportMentionsViaCachedLiveSourceEffect({
|
|
1278
|
+
...options,
|
|
1279
|
+
mode: "auto",
|
|
1280
|
+
}),
|
|
1281
|
+
);
|
|
1282
|
+
}
|