birdclaw 0.5.1 → 0.6.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 +44 -1
- package/README.md +50 -5
- package/package.json +3 -2
- package/scripts/browser-perf.mjs +1 -0
- package/scripts/start-test-server.mjs +16 -3
- package/src/cli.ts +376 -13
- package/src/components/AccountSwitcher.tsx +186 -0
- package/src/components/AppNav.tsx +27 -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 +452 -0
- package/src/components/SyncNowButton.tsx +57 -25
- package/src/components/ThemeSlider.tsx +55 -50
- package/src/components/TimelineCard.tsx +225 -93
- package/src/components/TimelineRouteFrame.tsx +22 -8
- package/src/components/TweetMediaGrid.tsx +87 -38
- package/src/components/TweetRichText.tsx +15 -11
- package/src/components/account-selection.ts +64 -0
- package/src/components/useTimelineRouteData.ts +23 -7
- package/src/lib/account-sync-job.ts +654 -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 +1364 -1300
- package/src/lib/authored-live.ts +261 -204
- package/src/lib/avatar-cache.ts +159 -44
- package/src/lib/backup.ts +1532 -951
- package/src/lib/bird-actions.ts +139 -57
- package/src/lib/bird-command.ts +101 -28
- package/src/lib/bird.ts +549 -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/conversation-surface.ts +79 -48
- package/src/lib/db.ts +33 -3
- 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/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/media-fetch.ts +286 -213
- package/src/lib/mention-threads-live.ts +352 -288
- package/src/lib/mentions-live.ts +390 -342
- 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/openai.ts +84 -53
- package/src/lib/period-digest.ts +953 -0
- package/src/lib/profile-affiliation-hydration.ts +93 -54
- 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 +969 -199
- package/src/lib/research.ts +165 -120
- package/src/lib/sqlite.ts +1 -0
- package/src/lib/timeline-collections-live.ts +204 -167
- package/src/lib/timeline-live.ts +60 -39
- package/src/lib/tweet-lookup.ts +30 -19
- package/src/lib/types.ts +38 -1
- package/src/lib/ui.ts +30 -7
- package/src/lib/url-expansion.ts +226 -55
- package/src/lib/url-safety.ts +220 -0
- package/src/lib/web-sync.ts +216 -148
- package/src/lib/whois.ts +166 -147
- package/src/lib/x-web.ts +102 -71
- package/src/lib/xurl.ts +681 -411
- package/src/routeTree.gen.ts +42 -0
- package/src/routes/__root.tsx +25 -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/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/period-digest.tsx +123 -0
- package/src/routes/api/profile-hydrate.tsx +31 -28
- package/src/routes/api/query.tsx +79 -55
- package/src/routes/api/status.tsx +18 -10
- package/src/routes/api/sync.tsx +75 -29
- package/src/routes/dms.tsx +95 -28
- package/src/routes/inbox.tsx +32 -19
- package/src/routes/today.tsx +441 -0
package/src/lib/authored-live.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Database } from "./sqlite";
|
|
2
|
+
import { Effect } from "effect";
|
|
2
3
|
import { getNativeDb } from "./db";
|
|
4
|
+
import { runEffectPromise, tryPromise } from "./effect-runtime";
|
|
3
5
|
import { readSyncCache, writeSyncCache } from "./sync-cache";
|
|
4
6
|
import type {
|
|
5
7
|
TweetEntities,
|
|
@@ -26,6 +28,15 @@ import {
|
|
|
26
28
|
|
|
27
29
|
export type AuthoredSyncMode = "xurl";
|
|
28
30
|
|
|
31
|
+
export interface SyncAuthoredTweetsOptions {
|
|
32
|
+
account?: string;
|
|
33
|
+
mode?: AuthoredSyncMode;
|
|
34
|
+
limit?: number;
|
|
35
|
+
maxPages?: number;
|
|
36
|
+
sinceId?: string;
|
|
37
|
+
untilId?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
export class AuthoredSyncError extends Error {
|
|
30
41
|
constructor(
|
|
31
42
|
message: string,
|
|
@@ -108,6 +119,17 @@ const AUTHORED_MEDIA_FIELDS = [
|
|
|
108
119
|
"alt_text",
|
|
109
120
|
];
|
|
110
121
|
|
|
122
|
+
function toError(error: unknown) {
|
|
123
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function trySync<T>(try_: () => T) {
|
|
127
|
+
return Effect.try({
|
|
128
|
+
try: try_,
|
|
129
|
+
catch: toError,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
111
133
|
function assertXurlLimit(limit: number) {
|
|
112
134
|
if (
|
|
113
135
|
!Number.isFinite(limit) ||
|
|
@@ -413,57 +435,65 @@ function userFromAuthenticatedPayload(
|
|
|
413
435
|
};
|
|
414
436
|
}
|
|
415
437
|
|
|
416
|
-
|
|
438
|
+
function resolveAuthoredIdentityEffect({
|
|
417
439
|
account,
|
|
418
440
|
db,
|
|
419
441
|
}: {
|
|
420
442
|
account?: string;
|
|
421
443
|
db: Database;
|
|
422
444
|
}) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
445
|
+
return Effect.gen(function* () {
|
|
446
|
+
const status = yield* tryPromise(() => getTransportStatus());
|
|
447
|
+
if (status.availableTransport !== "xurl") {
|
|
448
|
+
return yield* Effect.fail(new AuthoredSyncError(status.statusText, 4));
|
|
449
|
+
}
|
|
427
450
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
451
|
+
const resolvedAccount = yield* trySync(() => resolveAccount(db, account));
|
|
452
|
+
if (resolvedAccount.externalUserId) {
|
|
453
|
+
return {
|
|
454
|
+
...resolvedAccount,
|
|
455
|
+
userId: resolvedAccount.externalUserId,
|
|
456
|
+
authenticatedUser: undefined,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
436
459
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
460
|
+
const authenticated = yield* tryPromise(() => lookupAuthenticatedUser());
|
|
461
|
+
const authenticatedUser = userFromAuthenticatedPayload(authenticated);
|
|
462
|
+
if (!authenticatedUser?.id) {
|
|
463
|
+
return yield* Effect.fail(
|
|
464
|
+
new AuthoredSyncError(
|
|
465
|
+
"Could not resolve authenticated Twitter user id",
|
|
466
|
+
4,
|
|
467
|
+
),
|
|
468
|
+
);
|
|
469
|
+
}
|
|
445
470
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
471
|
+
if (
|
|
472
|
+
normalizeUsername(authenticatedUser.username) !==
|
|
473
|
+
normalizeUsername(resolvedAccount.username)
|
|
474
|
+
) {
|
|
475
|
+
return yield* Effect.fail(
|
|
476
|
+
new AuthoredSyncError(
|
|
477
|
+
`xurl is authenticated as @${authenticatedUser.username}, but selected account ${resolvedAccount.accountId} is @${resolvedAccount.username}. Link the account external_user_id or switch xurl login before syncing authored tweets.`,
|
|
478
|
+
4,
|
|
479
|
+
),
|
|
480
|
+
);
|
|
481
|
+
}
|
|
455
482
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
483
|
+
yield* trySync(() =>
|
|
484
|
+
persistAccountExternalUserId(
|
|
485
|
+
db,
|
|
486
|
+
resolvedAccount.accountId,
|
|
487
|
+
authenticatedUser.id,
|
|
488
|
+
),
|
|
489
|
+
);
|
|
461
490
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
491
|
+
return {
|
|
492
|
+
...resolvedAccount,
|
|
493
|
+
userId: authenticatedUser.id,
|
|
494
|
+
authenticatedUser,
|
|
495
|
+
};
|
|
496
|
+
});
|
|
467
497
|
}
|
|
468
498
|
|
|
469
499
|
function toFallbackUser({
|
|
@@ -887,188 +917,215 @@ function buildResult({
|
|
|
887
917
|
};
|
|
888
918
|
}
|
|
889
919
|
|
|
890
|
-
export
|
|
920
|
+
export function syncAuthoredTweetsEffect({
|
|
891
921
|
account,
|
|
892
922
|
mode = "xurl",
|
|
893
923
|
limit = DEFAULT_LIMIT,
|
|
894
924
|
maxPages,
|
|
895
925
|
sinceId,
|
|
896
926
|
untilId,
|
|
897
|
-
}: {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}) {
|
|
905
|
-
if (mode !== "xurl") {
|
|
906
|
-
throw new Error("authored sync only supports --mode xurl");
|
|
907
|
-
}
|
|
927
|
+
}: SyncAuthoredTweetsOptions) {
|
|
928
|
+
return Effect.gen(function* () {
|
|
929
|
+
if (mode !== "xurl") {
|
|
930
|
+
return yield* Effect.fail(
|
|
931
|
+
new Error("authored sync only supports --mode xurl"),
|
|
932
|
+
);
|
|
933
|
+
}
|
|
908
934
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
sinceId === undefined && !untilId && cursor.state === "pending-forward";
|
|
916
|
-
const usePersistedUntil =
|
|
917
|
-
sinceId === undefined &&
|
|
918
|
-
Boolean(untilId) &&
|
|
919
|
-
cursor.state === "pending-until" &&
|
|
920
|
-
cursor.untilId === untilId;
|
|
921
|
-
const shouldSeedFromArchive =
|
|
922
|
-
!usePersistedForward &&
|
|
923
|
-
!cursor.sinceId &&
|
|
924
|
-
sinceId === undefined &&
|
|
925
|
-
!untilId;
|
|
926
|
-
const archiveSinceSeed = shouldSeedFromArchive
|
|
927
|
-
? findArchiveAuthoredSinceSeed(db, identity.accountId)
|
|
928
|
-
: null;
|
|
929
|
-
if (shouldSeedFromArchive && !archiveSinceSeed) {
|
|
930
|
-
console.error(
|
|
931
|
-
"birdclaw sync authored: no archive baseline found; starting a full backwards scan",
|
|
935
|
+
const pageLimit = yield* trySync(() => assertXurlLimit(limit));
|
|
936
|
+
const parsedMaxPages = yield* trySync(() => parseMaxPages(maxPages));
|
|
937
|
+
const db = yield* trySync(() => getNativeDb());
|
|
938
|
+
const identity = yield* resolveAuthoredIdentityEffect({ account, db });
|
|
939
|
+
const cursor = yield* trySync(() =>
|
|
940
|
+
readAuthoredCursor(db, identity.accountId),
|
|
932
941
|
);
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
942
|
+
const usePersistedForward =
|
|
943
|
+
sinceId === undefined && !untilId && cursor.state === "pending-forward";
|
|
944
|
+
const usePersistedUntil =
|
|
945
|
+
sinceId === undefined &&
|
|
946
|
+
Boolean(untilId) &&
|
|
947
|
+
cursor.state === "pending-until" &&
|
|
948
|
+
cursor.untilId === untilId;
|
|
949
|
+
const shouldSeedFromArchive =
|
|
950
|
+
!usePersistedForward &&
|
|
951
|
+
!cursor.sinceId &&
|
|
952
|
+
sinceId === undefined &&
|
|
953
|
+
!untilId;
|
|
954
|
+
const archiveSinceSeed = shouldSeedFromArchive
|
|
955
|
+
? yield* trySync(() =>
|
|
956
|
+
findArchiveAuthoredSinceSeed(db, identity.accountId),
|
|
957
|
+
)
|
|
958
|
+
: null;
|
|
959
|
+
if (shouldSeedFromArchive && !archiveSinceSeed) {
|
|
960
|
+
console.error(
|
|
961
|
+
"birdclaw sync authored: no archive baseline found; starting a full backwards scan",
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
const persistedUntilSinceId: string | null = usePersistedUntil
|
|
965
|
+
? (("requestedSinceId" in cursor
|
|
966
|
+
? cursor.requestedSinceId
|
|
967
|
+
: cursor.sinceId) ?? null)
|
|
968
|
+
: null;
|
|
969
|
+
const effectiveSinceId: string | null =
|
|
970
|
+
sinceId ??
|
|
971
|
+
archiveSinceSeed ??
|
|
972
|
+
(untilId ? persistedUntilSinceId : cursor.sinceId) ??
|
|
973
|
+
null;
|
|
974
|
+
let nextToken = usePersistedForward
|
|
947
975
|
? cursor.token
|
|
948
|
-
:
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
976
|
+
: usePersistedUntil
|
|
977
|
+
? cursor.token
|
|
978
|
+
: undefined;
|
|
979
|
+
let newestSeenId = usePersistedForward
|
|
980
|
+
? maxTweetId(cursor.sinceId, cursor.pendingNewestId)
|
|
981
|
+
: cursor.sinceId;
|
|
982
|
+
const pages: XurlUserTweetsResponse[] = [];
|
|
983
|
+
const sourceUser = toFallbackUser({
|
|
984
|
+
userId: identity.userId,
|
|
985
|
+
username: identity.username,
|
|
986
|
+
authenticatedUser: identity.authenticatedUser,
|
|
987
|
+
});
|
|
988
|
+
let pageCount = 0;
|
|
989
|
+
|
|
990
|
+
while (parsedMaxPages === null || pageCount < parsedMaxPages) {
|
|
991
|
+
const fetched = yield* tryPromise(() =>
|
|
992
|
+
listUserTweets(identity.userId, {
|
|
993
|
+
maxResults: pageLimit,
|
|
994
|
+
paginationToken: nextToken,
|
|
995
|
+
excludeRetweets: false,
|
|
996
|
+
sinceId: effectiveSinceId ?? undefined,
|
|
997
|
+
untilId,
|
|
998
|
+
tweetFields: AUTHORED_TWEET_FIELDS,
|
|
999
|
+
expansions: AUTHORED_EXPANSIONS,
|
|
1000
|
+
userFields: AUTHORED_USER_FIELDS,
|
|
1001
|
+
mediaFields: AUTHORED_MEDIA_FIELDS,
|
|
1002
|
+
auth: "oauth2",
|
|
1003
|
+
}),
|
|
1004
|
+
).pipe(
|
|
1005
|
+
Effect.map((page) => ({ ok: true as const, page })),
|
|
1006
|
+
Effect.catchAll((error) =>
|
|
1007
|
+
Effect.succeed({ ok: false as const, error }),
|
|
1008
|
+
),
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
if (!fetched.ok) {
|
|
1012
|
+
if (pages.length === 0) {
|
|
1013
|
+
return yield* Effect.fail(fetched.error);
|
|
1014
|
+
}
|
|
1015
|
+
const payload = mergePages({
|
|
1016
|
+
pages,
|
|
1017
|
+
userId: identity.userId,
|
|
1018
|
+
nextToken: nextToken ?? null,
|
|
1019
|
+
});
|
|
1020
|
+
return buildResult({
|
|
1021
|
+
accountId: identity.accountId,
|
|
1022
|
+
userId: identity.userId,
|
|
1023
|
+
effectiveSinceId,
|
|
1024
|
+
nextSinceId: untilId ? cursor.sinceId : effectiveSinceId,
|
|
1025
|
+
nextToken: nextToken ?? null,
|
|
1026
|
+
pageCount,
|
|
1027
|
+
payload,
|
|
1028
|
+
partial: true,
|
|
1029
|
+
error: formatError(fetched.error),
|
|
1030
|
+
});
|
|
978
1031
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
});
|
|
984
|
-
return buildResult({
|
|
985
|
-
accountId: identity.accountId,
|
|
1032
|
+
|
|
1033
|
+
const page = fetched.page;
|
|
1034
|
+
const pagePayload = mergePages({
|
|
1035
|
+
pages: [page],
|
|
986
1036
|
userId: identity.userId,
|
|
987
|
-
|
|
988
|
-
nextSinceId: untilId ? cursor.sinceId : effectiveSinceId,
|
|
989
|
-
nextToken: nextToken ?? null,
|
|
990
|
-
pageCount,
|
|
991
|
-
payload,
|
|
992
|
-
partial: true,
|
|
993
|
-
error: formatError(error),
|
|
1037
|
+
nextToken: page.nextToken,
|
|
994
1038
|
});
|
|
995
|
-
|
|
1039
|
+
yield* trySync(() =>
|
|
1040
|
+
mergeAuthoredPayloadIntoLocalStore({
|
|
1041
|
+
db,
|
|
1042
|
+
accountId: identity.accountId,
|
|
1043
|
+
payload: pagePayload,
|
|
1044
|
+
sourceUser,
|
|
1045
|
+
}),
|
|
1046
|
+
);
|
|
1047
|
+
pages.push(page);
|
|
1048
|
+
pageCount += 1;
|
|
1049
|
+
newestSeenId = maxTweetId(newestSeenId, pagePayload.meta.newest_id);
|
|
1050
|
+
nextToken = page.nextToken ?? undefined;
|
|
996
1051
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
}
|
|
1020
|
-
} else if (nextToken) {
|
|
1021
|
-
writePendingForwardCursor(db, identity.accountId, {
|
|
1022
|
-
sinceId: effectiveSinceId,
|
|
1023
|
-
token: nextToken,
|
|
1024
|
-
pendingNewestId: newestSeenId,
|
|
1025
|
-
});
|
|
1052
|
+
const pendingToken = nextToken;
|
|
1053
|
+
if (pendingToken && untilId) {
|
|
1054
|
+
yield* trySync(() =>
|
|
1055
|
+
writePendingUntilCursor(db, identity.accountId, {
|
|
1056
|
+
sinceId: cursor.sinceId,
|
|
1057
|
+
token: pendingToken,
|
|
1058
|
+
untilId,
|
|
1059
|
+
requestedSinceId: effectiveSinceId,
|
|
1060
|
+
}),
|
|
1061
|
+
);
|
|
1062
|
+
} else if (pendingToken) {
|
|
1063
|
+
yield* trySync(() =>
|
|
1064
|
+
writePendingForwardCursor(db, identity.accountId, {
|
|
1065
|
+
sinceId: effectiveSinceId,
|
|
1066
|
+
token: pendingToken,
|
|
1067
|
+
pendingNewestId: newestSeenId,
|
|
1068
|
+
}),
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
if (!nextToken) {
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1026
1075
|
}
|
|
1027
1076
|
|
|
1028
|
-
|
|
1029
|
-
|
|
1077
|
+
const capped = Boolean(nextToken);
|
|
1078
|
+
const nextSinceId = untilId
|
|
1079
|
+
? cursor.sinceId
|
|
1080
|
+
: capped
|
|
1081
|
+
? effectiveSinceId
|
|
1082
|
+
: maxTweetId(newestSeenId, effectiveSinceId, cursor.sinceId);
|
|
1083
|
+
if (untilId && capped && nextToken) {
|
|
1084
|
+
yield* trySync(() =>
|
|
1085
|
+
writePendingUntilCursor(db, identity.accountId, {
|
|
1086
|
+
sinceId: cursor.sinceId,
|
|
1087
|
+
token: nextToken,
|
|
1088
|
+
untilId,
|
|
1089
|
+
requestedSinceId: effectiveSinceId,
|
|
1090
|
+
}),
|
|
1091
|
+
);
|
|
1092
|
+
} else if (untilId) {
|
|
1093
|
+
yield* trySync(() =>
|
|
1094
|
+
writeCommittedCursor(db, identity.accountId, cursor.sinceId),
|
|
1095
|
+
);
|
|
1096
|
+
} else if (capped && nextToken) {
|
|
1097
|
+
yield* trySync(() =>
|
|
1098
|
+
writePendingForwardCursor(db, identity.accountId, {
|
|
1099
|
+
sinceId: nextSinceId,
|
|
1100
|
+
token: nextToken,
|
|
1101
|
+
pendingNewestId: newestSeenId,
|
|
1102
|
+
}),
|
|
1103
|
+
);
|
|
1104
|
+
} else {
|
|
1105
|
+
yield* trySync(() =>
|
|
1106
|
+
writeCommittedCursor(db, identity.accountId, nextSinceId),
|
|
1107
|
+
);
|
|
1030
1108
|
}
|
|
1031
|
-
}
|
|
1032
1109
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
? effectiveSinceId
|
|
1038
|
-
: maxTweetId(newestSeenId, effectiveSinceId, cursor.sinceId);
|
|
1039
|
-
if (untilId && capped && nextToken) {
|
|
1040
|
-
writePendingUntilCursor(db, identity.accountId, {
|
|
1041
|
-
sinceId: cursor.sinceId,
|
|
1042
|
-
token: nextToken,
|
|
1043
|
-
untilId,
|
|
1044
|
-
requestedSinceId: effectiveSinceId,
|
|
1110
|
+
const payload = mergePages({
|
|
1111
|
+
pages,
|
|
1112
|
+
userId: identity.userId,
|
|
1113
|
+
nextToken: nextToken ?? null,
|
|
1045
1114
|
});
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1115
|
+
return buildResult({
|
|
1116
|
+
accountId: identity.accountId,
|
|
1117
|
+
userId: identity.userId,
|
|
1118
|
+
effectiveSinceId,
|
|
1119
|
+
nextSinceId,
|
|
1120
|
+
nextToken: nextToken ?? null,
|
|
1121
|
+
pageCount,
|
|
1122
|
+
payload,
|
|
1123
|
+
partial: capped,
|
|
1124
|
+
...(capped ? { error: "max pages reached before sync completed" } : {}),
|
|
1053
1125
|
});
|
|
1054
|
-
} else {
|
|
1055
|
-
writeCommittedCursor(db, identity.accountId, nextSinceId);
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
const payload = mergePages({
|
|
1059
|
-
pages,
|
|
1060
|
-
userId: identity.userId,
|
|
1061
|
-
nextToken: nextToken ?? null,
|
|
1062
|
-
});
|
|
1063
|
-
return buildResult({
|
|
1064
|
-
accountId: identity.accountId,
|
|
1065
|
-
userId: identity.userId,
|
|
1066
|
-
effectiveSinceId,
|
|
1067
|
-
nextSinceId,
|
|
1068
|
-
nextToken: nextToken ?? null,
|
|
1069
|
-
pageCount,
|
|
1070
|
-
payload,
|
|
1071
|
-
partial: capped,
|
|
1072
|
-
...(capped ? { error: "max pages reached before sync completed" } : {}),
|
|
1073
1126
|
});
|
|
1074
1127
|
}
|
|
1128
|
+
|
|
1129
|
+
export function syncAuthoredTweets(options: SyncAuthoredTweetsOptions) {
|
|
1130
|
+
return runEffectPromise(syncAuthoredTweetsEffect(options));
|
|
1131
|
+
}
|