birdclaw 0.8.4 → 0.9.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 +31 -0
- package/README.md +22 -2
- package/dist/cli/birdclaw.js +1233 -192
- package/dist/client/assets/AvatarChip-C8zELEVt.js +1 -0
- package/dist/client/assets/ProfilePreview-CsiLpFrb.js +1 -0
- package/dist/client/assets/SavedTimelineView-DE4CXK-h.js +1 -0
- package/dist/client/assets/SmartTimestamp-BypN4KPq.js +1 -0
- package/dist/client/assets/TimelineRouteFrame-D6djyMAT.js +1 -0
- package/dist/client/assets/blocks-h27nF3TL.js +1 -0
- package/dist/client/assets/{bookmarks-CbBkMvXk.js → bookmarks-Cprrli-V.js} +1 -1
- package/dist/client/assets/circle-check-BCK_qvXt.js +1 -0
- package/dist/client/assets/client-stream-contracts-D1QUdWfh.js +5 -0
- package/dist/client/assets/createLucideIcon-CMLcIxOz.js +1 -0
- package/dist/client/assets/data-sources-Beb3JZkl.js +1 -0
- package/dist/client/assets/discuss-ciwVz7Fo.js +1 -0
- package/dist/client/assets/dms-BZ3GUPey.js +1 -0
- package/dist/client/assets/inbox-Dajdut79.js +1 -0
- package/dist/client/assets/index-a1ZAcWSa.js +31 -0
- package/dist/client/assets/likes-CxZZqiZC.js +1 -0
- package/dist/client/assets/links-CsWS9uKL.js +1 -0
- package/dist/client/assets/mapbox-dx7h2mOm.js +2 -0
- package/dist/client/assets/{mapbox-gl-dgf_hX3a.js → mapbox-gl-WUhsQcSS.js} +1 -1
- package/dist/client/assets/{mentions-CxKCgzIf.js → mentions-C3JYXV2K.js} +1 -1
- package/dist/client/assets/network-map-D4XA7TTO.js +2 -0
- package/dist/client/assets/profile-analyze-QAznTMjZ.js +1 -0
- package/dist/client/assets/{profile-hydration-client-YEtJsNTv.js → profile-hydration-client-BQyC5--e.js} +1 -1
- package/dist/client/assets/profiles._handle-qntfbCE1.js +1 -0
- package/dist/client/assets/query-client-DZ5C7UNo.js +17 -0
- package/dist/client/assets/rate-limits-eHs_5dDB.js +1 -0
- package/dist/client/assets/react-BwGzW7mM.js +1 -0
- package/dist/client/assets/react-dom-DEMAB80t.js +1 -0
- package/dist/client/assets/{repeat-2-xCwHM9hD.js → repeat-2-CQ0lek01.js} +1 -1
- package/dist/client/assets/rolldown-runtime-B-9mzPXz.js +1 -0
- package/dist/client/assets/{routes-q7ZaW4En.js → routes-CeIv5bkV.js} +1 -1
- package/dist/client/assets/schemas-CntsNLmN.js +64 -0
- package/dist/client/assets/search-C_LqodNa.js +1 -0
- package/dist/client/assets/{sparkles-CQVlvHsZ.js → sparkles-Gx_BHkDX.js} +1 -1
- package/dist/client/assets/styles-DTMajAmk.css +2 -0
- package/dist/client/assets/today-DxppbQGF.js +1 -0
- package/dist/client/assets/{triangle-alert-CBL7s2pf.js → triangle-alert-DJPCW6jQ.js} +1 -1
- package/dist/client/assets/useTimelineRouteData-CWvBmHLE.js +1 -0
- package/dist/client/assets/{users-CHbKlqUr.js → users-DI3w8Mol.js} +1 -1
- package/dist/server/assets/{AvatarChip-zUDAEmjL.js → AvatarChip-DWag3IzT.js} +1 -1
- package/dist/server/assets/{FeedState-ntW0Q2GB.js → FeedState-CwzcvP-p.js} +1 -1
- package/dist/server/assets/{ProfileAnalysisStream-BFvhVdhn.js → ProfileAnalysisStream-CLjrYzEx.js} +2 -2
- package/dist/server/assets/{ProfilePreview-C6gAor_j.js → ProfilePreview-Bq-DSUM2.js} +7 -8
- package/dist/server/assets/{SavedTimelineView-BG1jnBp1.js → SavedTimelineView-Bk9nfnb-.js} +2 -2
- package/dist/server/assets/{SmartTimestamp-DfwE1NGf.js → SmartTimestamp-C06w_f9J.js} +1 -1
- package/dist/server/assets/SyncNowButton-CKuXKYcd.js +259 -0
- package/dist/server/assets/{TimelineRouteFrame-CU7ilM7U.js → TimelineRouteFrame-Bg3e7X_N.js} +5 -3
- package/dist/server/assets/{TweetRichText-DhCVv1-6.js → TweetRichText-wzUAtbPS.js} +2 -2
- package/dist/server/assets/{_tanstack-start-manifest_v-C-ZRnG_n.js → _tanstack-start-manifest_v-CjYYXQ8Z.js} +35 -35
- package/dist/server/assets/{blocks-DSgQghqP.js → blocks-CaP_u3mY.js} +3 -3
- package/dist/server/assets/{blocks-C1mgGTB3.js → blocks-DKy4yTF-.js} +3 -3
- package/dist/server/assets/{bookmarks-BJN8tykY.js → bookmarks-CPFq3JVX.js} +1 -1
- package/dist/server/assets/{client-stream-contracts-C2xbWVjm.js → client-stream-contracts-gtXEreFY.js} +72 -8
- package/dist/server/assets/{data-sources-BnA1Q94p.js → data-sources-DWwmQqcO.js} +1 -1
- package/dist/server/assets/{discuss-CJ1dau1j.js → discuss-BB8wFy4L.js} +3 -3
- package/dist/server/assets/{discuss-QTo08NCN.js → discuss-CVGB4_I0.js} +3 -3
- package/dist/server/assets/{dms-DAyQrVM2.js → dms-7NzdyQZQ.js} +6 -6
- package/dist/server/assets/{dms-BuLj4bp8.js → dms-vbEumuuO.js} +4 -4
- package/dist/server/assets/{empty-plugin-adapters-DzrEkdBF.js → empty-plugin-adapters-D9UWiqvJ.js} +1 -1
- package/dist/server/assets/{inbox-CkGOaW1q.js → inbox-CBeZctFd.js} +2 -2
- package/dist/server/assets/{inbox-DpWd1EzC.js → inbox-UpHhXODd.js} +4 -4
- package/dist/server/assets/{likes-DBOAtsJU.js → likes-CDAjYyTm.js} +1 -1
- package/dist/server/assets/{links-DAhSjynu.js → links-DU-TIHtg.js} +6 -6
- package/dist/server/assets/{links-DmH5lxHO.js → links-sQ3q2jvO.js} +6 -6
- package/dist/server/assets/{mentions-BH2MzKtB.js → mentions-DdndGSRw.js} +1 -1
- package/dist/server/assets/{network-map-D3CzRcC4.js → network-map-B9iOapuB.js} +2 -2
- package/dist/server/assets/{network-map-D5DoLOwR.js → network-map-CcOz-T9O.js} +2 -2
- package/dist/server/assets/{profile-analyze-C1NoM0tV.js → profile-analyze-BmDeTif5.js} +1 -1
- package/dist/server/assets/{profile-analyze-BP65PED0.js → profile-analyze-CUfXmXEH.js} +3 -3
- package/dist/server/assets/{profiles._handle-DsLNj2Ca.js → profiles._handle-8snagiR1.js} +4 -4
- package/dist/server/assets/{profiles._handle-DjwlN_ac.js → profiles._handle-BAqnghwo.js} +5 -5
- package/dist/server/assets/{rate-limits-VAr2qzYD.js → rate-limits-BY2-aQPT.js} +1 -1
- package/dist/server/assets/{router-DqkwSPiM.js → router-DP7iZD-q.js} +424 -128
- package/dist/server/assets/{routes-A9c9ZhwN.js → routes-D9226vKz.js} +1 -1
- package/dist/server/assets/start-5Z2QO8AU.js +4 -0
- package/dist/server/assets/{today-B5hVM57Z.js → today-BmvIMJzI.js} +3 -3
- package/dist/server/assets/{today-fvGiZ7ud.js → today-DKWphGrC.js} +112 -57
- package/dist/server/assets/{ui-qbmcMSSX.js → ui-rDHeS-cc.js} +2 -1
- package/dist/server/assets/{useTimelineRouteData-27cxynxa.js → useTimelineRouteData-BmDbGvQ8.js} +7 -7
- package/dist/server/server.js +50 -3413
- package/package.json +13 -13
- package/dist/client/assets/AvatarChip-DLoqM5xD.js +0 -1
- package/dist/client/assets/ProfilePreview-BejAuYsJ.js +0 -1
- package/dist/client/assets/SavedTimelineView-BIS7HKpp.js +0 -1
- package/dist/client/assets/SmartTimestamp-BR0-9se2.js +0 -1
- package/dist/client/assets/TimelineRouteFrame-CyGPj0eM.js +0 -1
- package/dist/client/assets/blocks-7UDKQp8H.js +0 -1
- package/dist/client/assets/chunk-C0phanGV.js +0 -1
- package/dist/client/assets/circle-check-C_Vq3ElD.js +0 -1
- package/dist/client/assets/client-stream-contracts-ys3XhWG4.js +0 -5
- package/dist/client/assets/createLucideIcon-JV8isFFR.js +0 -1
- package/dist/client/assets/data-sources-Cm2bpNvg.js +0 -1
- package/dist/client/assets/discuss-DTjaOugw.js +0 -1
- package/dist/client/assets/dms-CGd6Eogq.js +0 -1
- package/dist/client/assets/inbox-BHlGuOy2.js +0 -1
- package/dist/client/assets/index-CEIG7S6s.js +0 -31
- package/dist/client/assets/likes-cLrcAQem.js +0 -1
- package/dist/client/assets/links-CM9QO1LC.js +0 -1
- package/dist/client/assets/mapbox-BxTlqXt9.js +0 -2
- package/dist/client/assets/network-map-B5a3whqj.js +0 -2
- package/dist/client/assets/profile-analyze-CRtNEwlb.js +0 -1
- package/dist/client/assets/profiles._handle-C82_HoKX.js +0 -1
- package/dist/client/assets/query-client-fYu2rzRC.js +0 -17
- package/dist/client/assets/rate-limits-ScaEwdVK.js +0 -1
- package/dist/client/assets/react-dom-DHl6Ug7F.js +0 -1
- package/dist/client/assets/react-nBylq1U5.js +0 -1
- package/dist/client/assets/schemas-I9r6F6ya.js +0 -64
- package/dist/client/assets/search-Dq5kYsxA.js +0 -1
- package/dist/client/assets/styles-B8s3tt3r.css +0 -2
- package/dist/client/assets/today-CeG1O7Py.js +0 -1
- package/dist/client/assets/useTimelineRouteData-1HsqgvUi.js +0 -1
- package/dist/server/assets/SyncNowButton-BOn_IBXS.js +0 -76
- package/dist/server/assets/start-FaDOTmto.js +0 -4
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { $t as
|
|
1
|
+
import { $t as sidebarBrandMarkClass, Ct as navLinkLabelCompactClass, Qt as sidebarBrandCopyCompactClass, St as navLinkLabelClass, Xt as sidebarBrandClass, Zt as sidebarBrandCopyClass, an as sidebarShellCompactClass, bt as navLinkCompactClass, en as sidebarBrandTaglineClass, gt as mainColumnDmClass, ht as mainColumnClass, in as sidebarShellClass, m as cx, nn as sidebarFooterClass, o as bodyClass, on as siteShellClass, rn as sidebarNavClass, tn as sidebarBrandTitleClass, xt as navLinkIconClass, yt as navLinkClass } from "./ui-rDHeS-cc.js";
|
|
2
2
|
import { C as networkMapResponseSchema, D as tweetConversationResponseSchema, E as queryResponseSchema, O as webSyncJobSchema, S as liveDataSourcesResponseSchema, T as queryEnvelopeSchema, _ as blockListResponseSchema, a as fetchQueryEnvelope, b as linkInsightResponseSchema, d as runEffectPromise, f as toError$17, g as actionResponseSchemaFor, h as actionRequestSchema, k as xurlRateLimitSnapshotSchema, l as defaultRuntimeServices, m as trySync$11, n as queryKeys, p as tryPromise, t as BirdclawQueryProvider, u as runEffectBackground, w as profileHydrationResponseSchema, x as linkPreviewResponseSchema, y as inboxResponseSchema } from "./query-client-BdauHBgd.js";
|
|
3
|
-
import { t as AvatarChip } from "./AvatarChip-
|
|
3
|
+
import { t as AvatarChip } from "./AvatarChip-DWag3IzT.js";
|
|
4
4
|
import { n as setStoredAccountId, r as useSelectedAccountId } from "./account-selection-CTdFGWRk.js";
|
|
5
|
-
import { c as BirdclawMark } from "./FeedState-
|
|
6
|
-
import { a as assertSafePreviewUrl, h as tweetEntitiesFromXurl, i as __test__$1, l as displayUrlForLink, o as isBlockedAddress, s as safeHttpUrl, u as enrichFallbackUrlEntities } from "./ProfilePreview-
|
|
7
|
-
import { i as searchDiscussionStreamEventSchema, n as periodDigestStreamEventSchema, r as profileAnalysisStreamEventSchema } from "./client-stream-contracts-
|
|
8
|
-
import { t as Route$25 } from "./today-
|
|
9
|
-
import { t as Route$26 } from "./profile-analyze-
|
|
10
|
-
import { t as Route$27 } from "./network-map-
|
|
11
|
-
import { t as Route$28 } from "./links-
|
|
12
|
-
import { t as Route$29 } from "./inbox-
|
|
13
|
-
import { t as Route$30 } from "./dms-
|
|
14
|
-
import { t as Route$31 } from "./discuss-
|
|
15
|
-
import { t as Route$32 } from "./blocks-
|
|
16
|
-
import { t as Route$33 } from "./profiles._handle-
|
|
5
|
+
import { c as BirdclawMark } from "./FeedState-CwzcvP-p.js";
|
|
6
|
+
import { a as assertSafePreviewUrl, h as tweetEntitiesFromXurl, i as __test__$1, l as displayUrlForLink, o as isBlockedAddress, s as safeHttpUrl, u as enrichFallbackUrlEntities } from "./ProfilePreview-Bq-DSUM2.js";
|
|
7
|
+
import { i as searchDiscussionStreamEventSchema, n as periodDigestStreamEventSchema, r as profileAnalysisStreamEventSchema } from "./client-stream-contracts-gtXEreFY.js";
|
|
8
|
+
import { t as Route$25 } from "./today-BmvIMJzI.js";
|
|
9
|
+
import { t as Route$26 } from "./profile-analyze-BmDeTif5.js";
|
|
10
|
+
import { t as Route$27 } from "./network-map-B9iOapuB.js";
|
|
11
|
+
import { t as Route$28 } from "./links-DU-TIHtg.js";
|
|
12
|
+
import { t as Route$29 } from "./inbox-UpHhXODd.js";
|
|
13
|
+
import { t as Route$30 } from "./dms-7NzdyQZQ.js";
|
|
14
|
+
import { t as Route$31 } from "./discuss-BB8wFy4L.js";
|
|
15
|
+
import { t as Route$32 } from "./blocks-CaP_u3mY.js";
|
|
16
|
+
import { t as Route$33 } from "./profiles._handle-8snagiR1.js";
|
|
17
17
|
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
18
18
|
import { HeadContent, Link, Scripts, createFileRoute, createRootRoute, createRouter, lazyRouteComponent, useRouterState } from "@tanstack/react-router";
|
|
19
19
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -285,6 +285,7 @@ var cleanupThemeTransition = (root) => {
|
|
|
285
285
|
root.classList.remove("theme-transition");
|
|
286
286
|
root.style.removeProperty("--theme-switch-x");
|
|
287
287
|
root.style.removeProperty("--theme-switch-y");
|
|
288
|
+
root.querySelectorAll("[data-theme-transition-target]").forEach((element) => element.removeAttribute("data-theme-transition-target"));
|
|
288
289
|
};
|
|
289
290
|
function startThemeTransition({ nextTheme, currentTheme, setTheme, context }) {
|
|
290
291
|
if (currentTheme === nextTheme) return;
|
|
@@ -320,6 +321,7 @@ function startThemeTransition({ nextTheme, currentTheme, setTheme, context }) {
|
|
|
320
321
|
root.style.setProperty("--theme-switch-x", `${xPercent * 100}%`);
|
|
321
322
|
root.style.setProperty("--theme-switch-y", `${yPercent * 100}%`);
|
|
322
323
|
root.classList.add("theme-transition");
|
|
324
|
+
context?.element?.setAttribute("data-theme-transition-target", "");
|
|
323
325
|
try {
|
|
324
326
|
const transition = startViewTransition?.(() => {
|
|
325
327
|
applyTheme();
|
|
@@ -519,7 +521,7 @@ function AppNav({ compact = false }) {
|
|
|
519
521
|
}
|
|
520
522
|
//#endregion
|
|
521
523
|
//#region src/styles.css?url
|
|
522
|
-
var styles_default = "/assets/styles-
|
|
524
|
+
var styles_default = "/assets/styles-DTMajAmk.css";
|
|
523
525
|
//#endregion
|
|
524
526
|
//#region src/routes/__root.tsx
|
|
525
527
|
var Route$24 = createRootRoute({
|
|
@@ -572,31 +574,31 @@ function RootDocument({ children }) {
|
|
|
572
574
|
}
|
|
573
575
|
//#endregion
|
|
574
576
|
//#region src/routes/rate-limits.tsx
|
|
575
|
-
var $$splitComponentImporter$5 = () => import("./rate-limits-
|
|
577
|
+
var $$splitComponentImporter$5 = () => import("./rate-limits-BY2-aQPT.js");
|
|
576
578
|
var Route$23 = createFileRoute("/rate-limits")({ component: lazyRouteComponent($$splitComponentImporter$5, "component") });
|
|
577
579
|
//#endregion
|
|
578
580
|
//#region src/routes/mentions.tsx
|
|
579
|
-
var $$splitComponentImporter$4 = () => import("./mentions-
|
|
581
|
+
var $$splitComponentImporter$4 = () => import("./mentions-DdndGSRw.js");
|
|
580
582
|
var Route$22 = createFileRoute("/mentions")({ component: lazyRouteComponent($$splitComponentImporter$4, "component") });
|
|
581
583
|
//#endregion
|
|
582
584
|
//#region src/routes/likes.tsx
|
|
583
|
-
var $$splitComponentImporter$3 = () => import("./likes-
|
|
585
|
+
var $$splitComponentImporter$3 = () => import("./likes-CDAjYyTm.js");
|
|
584
586
|
var Route$21 = createFileRoute("/likes")({ component: lazyRouteComponent($$splitComponentImporter$3, "component") });
|
|
585
587
|
//#endregion
|
|
586
588
|
//#region src/routes/data-sources.tsx
|
|
587
|
-
var $$splitComponentImporter$2 = () => import("./data-sources-
|
|
589
|
+
var $$splitComponentImporter$2 = () => import("./data-sources-DWwmQqcO.js");
|
|
588
590
|
var Route$20 = createFileRoute("/data-sources")({ component: lazyRouteComponent($$splitComponentImporter$2, "component") });
|
|
589
591
|
//#endregion
|
|
590
592
|
//#region src/routes/bookmarks.tsx
|
|
591
|
-
var $$splitComponentImporter$1 = () => import("./bookmarks-
|
|
593
|
+
var $$splitComponentImporter$1 = () => import("./bookmarks-CPFq3JVX.js");
|
|
592
594
|
var Route$19 = createFileRoute("/bookmarks")({ component: lazyRouteComponent($$splitComponentImporter$1, "component") });
|
|
593
595
|
//#endregion
|
|
594
596
|
//#region src/routes/index.tsx
|
|
595
|
-
var $$splitComponentImporter = () => import("./routes-
|
|
597
|
+
var $$splitComponentImporter = () => import("./routes-D9226vKz.js");
|
|
596
598
|
var Route$18 = createFileRoute("/")({ component: lazyRouteComponent($$splitComponentImporter, "component") });
|
|
597
599
|
//#endregion
|
|
598
600
|
//#region src/lib/http-effect.ts
|
|
599
|
-
var LOCAL_HOSTS = new Set([
|
|
601
|
+
var LOCAL_HOSTS = /* @__PURE__ */ new Set([
|
|
600
602
|
"localhost",
|
|
601
603
|
"127.0.0.1",
|
|
602
604
|
"::1",
|
|
@@ -1809,6 +1811,43 @@ var BASE_SCHEMA_SQL = `
|
|
|
1809
1811
|
snapshot_id text not null
|
|
1810
1812
|
);
|
|
1811
1813
|
|
|
1814
|
+
create table if not exists x_lists (
|
|
1815
|
+
account_id text not null,
|
|
1816
|
+
list_id text not null,
|
|
1817
|
+
name text not null,
|
|
1818
|
+
description text not null default '',
|
|
1819
|
+
owner_profile_id text,
|
|
1820
|
+
owner_external_user_id text,
|
|
1821
|
+
is_private integer not null default 0,
|
|
1822
|
+
member_count integer,
|
|
1823
|
+
follower_count integer,
|
|
1824
|
+
source text not null,
|
|
1825
|
+
membership_status text not null default 'not_synced',
|
|
1826
|
+
lists_synced_at text not null,
|
|
1827
|
+
members_synced_at text,
|
|
1828
|
+
member_page_count integer not null default 0,
|
|
1829
|
+
member_result_count integer not null default 0,
|
|
1830
|
+
rate_limit_json text not null default '{}',
|
|
1831
|
+
raw_json text not null default '{}',
|
|
1832
|
+
updated_at text not null,
|
|
1833
|
+
primary key (account_id, list_id)
|
|
1834
|
+
);
|
|
1835
|
+
|
|
1836
|
+
create table if not exists x_list_members (
|
|
1837
|
+
account_id text not null,
|
|
1838
|
+
list_id text not null,
|
|
1839
|
+
profile_id text not null,
|
|
1840
|
+
external_user_id text not null,
|
|
1841
|
+
source text not null,
|
|
1842
|
+
current integer not null default 1,
|
|
1843
|
+
first_seen_at text not null,
|
|
1844
|
+
last_seen_at text not null,
|
|
1845
|
+
ended_at text,
|
|
1846
|
+
raw_json text not null default '{}',
|
|
1847
|
+
updated_at text not null,
|
|
1848
|
+
primary key (account_id, list_id, profile_id)
|
|
1849
|
+
);
|
|
1850
|
+
|
|
1812
1851
|
create table if not exists geocoded_locations (
|
|
1813
1852
|
normalized_key text primary key,
|
|
1814
1853
|
original text not null,
|
|
@@ -1881,6 +1920,9 @@ var INDEX_SQL = `
|
|
|
1881
1920
|
create index if not exists idx_follow_edges_profile on follow_edges(profile_id, current);
|
|
1882
1921
|
create index if not exists idx_follow_snapshots_account on follow_snapshots(account_id, direction, completed_at desc);
|
|
1883
1922
|
create index if not exists idx_follow_events_account on follow_events(account_id, direction, kind, event_at desc);
|
|
1923
|
+
create index if not exists idx_x_lists_account_name on x_lists(account_id, name collate nocase, list_id);
|
|
1924
|
+
create index if not exists idx_x_list_members_current on x_list_members(account_id, list_id, current, profile_id);
|
|
1925
|
+
create index if not exists idx_x_list_members_profile on x_list_members(profile_id, current, account_id, list_id);
|
|
1884
1926
|
create index if not exists idx_geocoded_locations_country on geocoded_locations(country_code);
|
|
1885
1927
|
create index if not exists idx_geocoded_locations_last_used on geocoded_locations(last_used_at desc);
|
|
1886
1928
|
create index if not exists idx_geocoded_unresolved_ttl on geocoded_locations_unresolved(ttl_until desc);
|
|
@@ -2096,6 +2138,50 @@ function ensureFollowGraphTables(db) {
|
|
|
2096
2138
|
);
|
|
2097
2139
|
`);
|
|
2098
2140
|
}
|
|
2141
|
+
function ensureXListTables(db) {
|
|
2142
|
+
db.exec(`
|
|
2143
|
+
create table if not exists x_lists (
|
|
2144
|
+
account_id text not null,
|
|
2145
|
+
list_id text not null,
|
|
2146
|
+
name text not null,
|
|
2147
|
+
description text not null default '',
|
|
2148
|
+
owner_profile_id text,
|
|
2149
|
+
owner_external_user_id text,
|
|
2150
|
+
is_private integer not null default 0,
|
|
2151
|
+
member_count integer,
|
|
2152
|
+
follower_count integer,
|
|
2153
|
+
source text not null,
|
|
2154
|
+
membership_status text not null default 'not_synced',
|
|
2155
|
+
lists_synced_at text not null,
|
|
2156
|
+
members_synced_at text,
|
|
2157
|
+
member_page_count integer not null default 0,
|
|
2158
|
+
member_result_count integer not null default 0,
|
|
2159
|
+
rate_limit_json text not null default '{}',
|
|
2160
|
+
raw_json text not null default '{}',
|
|
2161
|
+
updated_at text not null,
|
|
2162
|
+
primary key (account_id, list_id)
|
|
2163
|
+
);
|
|
2164
|
+
|
|
2165
|
+
create table if not exists x_list_members (
|
|
2166
|
+
account_id text not null,
|
|
2167
|
+
list_id text not null,
|
|
2168
|
+
profile_id text not null,
|
|
2169
|
+
external_user_id text not null,
|
|
2170
|
+
source text not null,
|
|
2171
|
+
current integer not null default 1,
|
|
2172
|
+
first_seen_at text not null,
|
|
2173
|
+
last_seen_at text not null,
|
|
2174
|
+
ended_at text,
|
|
2175
|
+
raw_json text not null default '{}',
|
|
2176
|
+
updated_at text not null,
|
|
2177
|
+
primary key (account_id, list_id, profile_id)
|
|
2178
|
+
);
|
|
2179
|
+
|
|
2180
|
+
create index if not exists idx_x_lists_account_name on x_lists(account_id, name collate nocase, list_id);
|
|
2181
|
+
create index if not exists idx_x_list_members_current on x_list_members(account_id, list_id, current, profile_id);
|
|
2182
|
+
create index if not exists idx_x_list_members_profile on x_list_members(profile_id, current, account_id, list_id);
|
|
2183
|
+
`);
|
|
2184
|
+
}
|
|
2099
2185
|
function backfillTweetCollections(db) {
|
|
2100
2186
|
const missingKinds = [["likes", "liked"], ["bookmarks", "bookmarked"]].filter(([, column]) => db.prepare(`
|
|
2101
2187
|
select 1
|
|
@@ -2198,32 +2284,42 @@ function normalizeTweetState(db) {
|
|
|
2198
2284
|
create index idx_tweets_quoted on tweets(quoted_tweet_id);
|
|
2199
2285
|
`);
|
|
2200
2286
|
}
|
|
2201
|
-
var DATABASE_MIGRATIONS = [
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
db
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2287
|
+
var DATABASE_MIGRATIONS = [
|
|
2288
|
+
{
|
|
2289
|
+
version: 1,
|
|
2290
|
+
name: "canonical schema baseline",
|
|
2291
|
+
up: (db) => {
|
|
2292
|
+
db.exec(BASE_SCHEMA_SQL);
|
|
2293
|
+
ensureAccountExternalUserIdColumn(db);
|
|
2294
|
+
ensureDmConversationInboxColumns(db);
|
|
2295
|
+
ensureTweetMetadataColumns(db);
|
|
2296
|
+
ensureProfileAvatarColumns(db);
|
|
2297
|
+
ensureTweetCollectionsTable(db);
|
|
2298
|
+
ensureTweetAccountEdgesTable(db);
|
|
2299
|
+
ensureProfileAffiliationsTable(db);
|
|
2300
|
+
ensureProfileSnapshotsTable(db);
|
|
2301
|
+
ensureProfileBioEntitiesTable(db);
|
|
2302
|
+
ensureIdentitySearchIndexTable(db);
|
|
2303
|
+
ensureLinkIndexTables(db);
|
|
2304
|
+
ensureFollowGraphTables(db);
|
|
2305
|
+
ensureSchemaIndexes(db);
|
|
2306
|
+
backfillTweetCollections(db);
|
|
2307
|
+
backfillTweetAccountEdges(db);
|
|
2308
|
+
}
|
|
2309
|
+
},
|
|
2310
|
+
{
|
|
2311
|
+
version: 2,
|
|
2312
|
+
name: "normalize tweet account and collection state",
|
|
2313
|
+
up: normalizeTweetState
|
|
2314
|
+
},
|
|
2315
|
+
{
|
|
2316
|
+
version: 3,
|
|
2317
|
+
name: "add durable X List metadata and membership",
|
|
2318
|
+
up: (db) => {
|
|
2319
|
+
ensureXListTables(db);
|
|
2320
|
+
}
|
|
2221
2321
|
}
|
|
2222
|
-
|
|
2223
|
-
version: 2,
|
|
2224
|
-
name: "normalize tweet account and collection state",
|
|
2225
|
-
up: normalizeTweetState
|
|
2226
|
-
}];
|
|
2322
|
+
];
|
|
2227
2323
|
function ensureDemoData(db) {
|
|
2228
2324
|
if (demoSeedAttempted) return;
|
|
2229
2325
|
seedDemoData(db);
|
|
@@ -2465,7 +2561,7 @@ function yearFromTimestamp(value) {
|
|
|
2465
2561
|
function pathLeaf(relativePath) {
|
|
2466
2562
|
return relativePath.split("/").at(-1) ?? "";
|
|
2467
2563
|
}
|
|
2468
|
-
var JSON_URL_KEYS = new Set([
|
|
2564
|
+
var JSON_URL_KEYS = /* @__PURE__ */ new Set([
|
|
2469
2565
|
"url",
|
|
2470
2566
|
"expandedUrl",
|
|
2471
2567
|
"expanded_url",
|
|
@@ -2739,6 +2835,107 @@ var definitions = {
|
|
|
2739
2835
|
]
|
|
2740
2836
|
}
|
|
2741
2837
|
},
|
|
2838
|
+
x_lists: {
|
|
2839
|
+
exportSql: `
|
|
2840
|
+
select account_id, list_id, name, description, owner_profile_id,
|
|
2841
|
+
owner_external_user_id, is_private, member_count, follower_count,
|
|
2842
|
+
source, membership_status, lists_synced_at, members_synced_at,
|
|
2843
|
+
member_page_count, member_result_count, rate_limit_json, raw_json,
|
|
2844
|
+
updated_at
|
|
2845
|
+
from x_lists
|
|
2846
|
+
order by account_id, name collate nocase, list_id
|
|
2847
|
+
`,
|
|
2848
|
+
...fixedShard("data/lists/lists.jsonl", "x_lists"),
|
|
2849
|
+
merge: {
|
|
2850
|
+
order: 9,
|
|
2851
|
+
sql: `
|
|
2852
|
+
insert into x_lists (
|
|
2853
|
+
account_id, list_id, name, description, owner_profile_id,
|
|
2854
|
+
owner_external_user_id, is_private, member_count, follower_count,
|
|
2855
|
+
source, membership_status, lists_synced_at, members_synced_at,
|
|
2856
|
+
member_page_count, member_result_count, rate_limit_json, raw_json,
|
|
2857
|
+
updated_at
|
|
2858
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2859
|
+
on conflict(account_id, list_id) do update set
|
|
2860
|
+
name = excluded.name,
|
|
2861
|
+
description = excluded.description,
|
|
2862
|
+
owner_profile_id = coalesce(excluded.owner_profile_id, x_lists.owner_profile_id),
|
|
2863
|
+
owner_external_user_id = coalesce(excluded.owner_external_user_id, x_lists.owner_external_user_id),
|
|
2864
|
+
is_private = case when excluded.updated_at >= x_lists.updated_at then excluded.is_private else x_lists.is_private end,
|
|
2865
|
+
member_count = coalesce(excluded.member_count, x_lists.member_count),
|
|
2866
|
+
follower_count = coalesce(excluded.follower_count, x_lists.follower_count),
|
|
2867
|
+
source = case when excluded.updated_at >= x_lists.updated_at then excluded.source else x_lists.source end,
|
|
2868
|
+
membership_status = case when excluded.updated_at >= x_lists.updated_at then excluded.membership_status else x_lists.membership_status end,
|
|
2869
|
+
lists_synced_at = max(x_lists.lists_synced_at, excluded.lists_synced_at),
|
|
2870
|
+
members_synced_at = nullif(max(coalesce(x_lists.members_synced_at, ''), coalesce(excluded.members_synced_at, '')), ''),
|
|
2871
|
+
member_page_count = case when excluded.updated_at >= x_lists.updated_at then excluded.member_page_count else x_lists.member_page_count end,
|
|
2872
|
+
member_result_count = case when excluded.updated_at >= x_lists.updated_at then excluded.member_result_count else x_lists.member_result_count end,
|
|
2873
|
+
rate_limit_json = case when excluded.updated_at >= x_lists.updated_at then excluded.rate_limit_json else x_lists.rate_limit_json end,
|
|
2874
|
+
raw_json = case when excluded.updated_at >= x_lists.updated_at then excluded.raw_json else x_lists.raw_json end,
|
|
2875
|
+
updated_at = max(x_lists.updated_at, excluded.updated_at)
|
|
2876
|
+
`,
|
|
2877
|
+
columns: [
|
|
2878
|
+
"account_id",
|
|
2879
|
+
"list_id",
|
|
2880
|
+
"name",
|
|
2881
|
+
"description",
|
|
2882
|
+
"owner_profile_id",
|
|
2883
|
+
"owner_external_user_id",
|
|
2884
|
+
"is_private",
|
|
2885
|
+
"member_count",
|
|
2886
|
+
"follower_count",
|
|
2887
|
+
"source",
|
|
2888
|
+
"membership_status",
|
|
2889
|
+
"lists_synced_at",
|
|
2890
|
+
"members_synced_at",
|
|
2891
|
+
"member_page_count",
|
|
2892
|
+
"member_result_count",
|
|
2893
|
+
"rate_limit_json",
|
|
2894
|
+
"raw_json",
|
|
2895
|
+
"updated_at"
|
|
2896
|
+
]
|
|
2897
|
+
}
|
|
2898
|
+
},
|
|
2899
|
+
x_list_members: {
|
|
2900
|
+
exportSql: `
|
|
2901
|
+
select account_id, list_id, profile_id, external_user_id, source,
|
|
2902
|
+
current, first_seen_at, last_seen_at, ended_at, raw_json, updated_at
|
|
2903
|
+
from x_list_members
|
|
2904
|
+
order by account_id, list_id, profile_id
|
|
2905
|
+
`,
|
|
2906
|
+
...fixedShard("data/lists/members.jsonl", "x_list_members"),
|
|
2907
|
+
merge: {
|
|
2908
|
+
order: 10,
|
|
2909
|
+
sql: `
|
|
2910
|
+
insert into x_list_members (
|
|
2911
|
+
account_id, list_id, profile_id, external_user_id, source, current,
|
|
2912
|
+
first_seen_at, last_seen_at, ended_at, raw_json, updated_at
|
|
2913
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2914
|
+
on conflict(account_id, list_id, profile_id) do update set
|
|
2915
|
+
external_user_id = coalesce(nullif(excluded.external_user_id, ''), x_list_members.external_user_id),
|
|
2916
|
+
source = case when excluded.updated_at >= x_list_members.updated_at then excluded.source else x_list_members.source end,
|
|
2917
|
+
current = case when excluded.updated_at >= x_list_members.updated_at then excluded.current else x_list_members.current end,
|
|
2918
|
+
first_seen_at = min(x_list_members.first_seen_at, excluded.first_seen_at),
|
|
2919
|
+
last_seen_at = max(x_list_members.last_seen_at, excluded.last_seen_at),
|
|
2920
|
+
ended_at = case when excluded.updated_at >= x_list_members.updated_at then excluded.ended_at else x_list_members.ended_at end,
|
|
2921
|
+
raw_json = case when excluded.updated_at >= x_list_members.updated_at then excluded.raw_json else x_list_members.raw_json end,
|
|
2922
|
+
updated_at = max(x_list_members.updated_at, excluded.updated_at)
|
|
2923
|
+
`,
|
|
2924
|
+
columns: [
|
|
2925
|
+
"account_id",
|
|
2926
|
+
"list_id",
|
|
2927
|
+
"profile_id",
|
|
2928
|
+
"external_user_id",
|
|
2929
|
+
"source",
|
|
2930
|
+
"current",
|
|
2931
|
+
"first_seen_at",
|
|
2932
|
+
"last_seen_at",
|
|
2933
|
+
"ended_at",
|
|
2934
|
+
"raw_json",
|
|
2935
|
+
"updated_at"
|
|
2936
|
+
]
|
|
2937
|
+
}
|
|
2938
|
+
},
|
|
2742
2939
|
tweets: {
|
|
2743
2940
|
exportSql: `
|
|
2744
2941
|
select id, author_profile_id, text, created_at, is_replied, reply_to_id,
|
|
@@ -2750,7 +2947,7 @@ var definitions = {
|
|
|
2750
2947
|
matchesPath: (candidate) => candidate.startsWith("data/tweets/"),
|
|
2751
2948
|
countKey: () => "tweets",
|
|
2752
2949
|
merge: {
|
|
2753
|
-
order:
|
|
2950
|
+
order: 11,
|
|
2754
2951
|
transform: sanitizeImportedTweets,
|
|
2755
2952
|
sql: `
|
|
2756
2953
|
insert into tweets (
|
|
@@ -2810,7 +3007,7 @@ var definitions = {
|
|
|
2810
3007
|
matchesPath: (candidate) => candidate.startsWith("data/collections/"),
|
|
2811
3008
|
countKey: (candidate) => `collections_${pathLeaf(candidate).replace(/\.jsonl$/, "") || "unknown"}`,
|
|
2812
3009
|
merge: {
|
|
2813
|
-
order:
|
|
3010
|
+
order: 12,
|
|
2814
3011
|
sql: `
|
|
2815
3012
|
insert into tweet_collections (
|
|
2816
3013
|
account_id, tweet_id, kind, collected_at, source, raw_json, updated_at
|
|
@@ -2848,7 +3045,7 @@ var definitions = {
|
|
|
2848
3045
|
matchesPath: (candidate) => candidate.startsWith("data/timeline_edges/"),
|
|
2849
3046
|
countKey: (candidate) => `timeline_edges_${pathLeaf(candidate).replace(/\.jsonl$/, "") || "unknown"}`,
|
|
2850
3047
|
merge: {
|
|
2851
|
-
order:
|
|
3048
|
+
order: 13,
|
|
2852
3049
|
sql: `
|
|
2853
3050
|
insert into tweet_account_edges (
|
|
2854
3051
|
account_id, tweet_id, kind, first_seen_at, last_seen_at, seen_count,
|
|
@@ -2887,7 +3084,7 @@ var definitions = {
|
|
|
2887
3084
|
`,
|
|
2888
3085
|
...fixedShard("data/dms/conversations.jsonl", "dm_conversations"),
|
|
2889
3086
|
merge: {
|
|
2890
|
-
order:
|
|
3087
|
+
order: 14,
|
|
2891
3088
|
sql: `
|
|
2892
3089
|
insert into dm_conversations (
|
|
2893
3090
|
id, account_id, participant_profile_id, title, inbox_kind, last_message_at, unread_count, needs_reply
|
|
@@ -2928,7 +3125,7 @@ var definitions = {
|
|
|
2928
3125
|
matchesPath: (candidate) => candidate.startsWith("data/dms/") && candidate !== "data/dms/conversations.jsonl",
|
|
2929
3126
|
countKey: () => "dm_messages",
|
|
2930
3127
|
merge: {
|
|
2931
|
-
order:
|
|
3128
|
+
order: 15,
|
|
2932
3129
|
sql: `
|
|
2933
3130
|
insert into dm_messages (
|
|
2934
3131
|
id, conversation_id, sender_profile_id, text, created_at, direction, is_replied, media_count
|
|
@@ -2972,7 +3169,7 @@ var definitions = {
|
|
|
2972
3169
|
`,
|
|
2973
3170
|
...fixedShard("data/links/url_expansions.jsonl", "url_expansions"),
|
|
2974
3171
|
merge: {
|
|
2975
|
-
order:
|
|
3172
|
+
order: 16,
|
|
2976
3173
|
transform: sanitizeImportedUrlExpansions,
|
|
2977
3174
|
sql: `
|
|
2978
3175
|
insert into url_expansions (
|
|
@@ -3020,7 +3217,7 @@ var definitions = {
|
|
|
3020
3217
|
`,
|
|
3021
3218
|
...fixedShard("data/links/occurrences.jsonl", "link_occurrences"),
|
|
3022
3219
|
merge: {
|
|
3023
|
-
order:
|
|
3220
|
+
order: 17,
|
|
3024
3221
|
sql: `
|
|
3025
3222
|
insert into link_occurrences (
|
|
3026
3223
|
source_kind, source_id, source_position, short_url, account_id,
|
|
@@ -3052,7 +3249,7 @@ var definitions = {
|
|
|
3052
3249
|
`,
|
|
3053
3250
|
...fixedShard("data/moderation/blocks.jsonl", "blocks"),
|
|
3054
3251
|
merge: {
|
|
3055
|
-
order:
|
|
3252
|
+
order: 18,
|
|
3056
3253
|
sql: `
|
|
3057
3254
|
insert into blocks (account_id, profile_id, source, created_at)
|
|
3058
3255
|
values (?, ?, ?, ?)
|
|
@@ -3076,7 +3273,7 @@ var definitions = {
|
|
|
3076
3273
|
`,
|
|
3077
3274
|
...fixedShard("data/moderation/mutes.jsonl", "mutes"),
|
|
3078
3275
|
merge: {
|
|
3079
|
-
order:
|
|
3276
|
+
order: 19,
|
|
3080
3277
|
sql: `
|
|
3081
3278
|
insert into mutes (account_id, profile_id, source, created_at)
|
|
3082
3279
|
values (?, ?, ?, ?)
|
|
@@ -3100,7 +3297,7 @@ var definitions = {
|
|
|
3100
3297
|
`,
|
|
3101
3298
|
...fixedShard("data/actions/tweet_actions.jsonl", "tweet_actions"),
|
|
3102
3299
|
merge: {
|
|
3103
|
-
order:
|
|
3300
|
+
order: 20,
|
|
3104
3301
|
sql: `
|
|
3105
3302
|
insert into tweet_actions (id, account_id, tweet_id, kind, body, created_at)
|
|
3106
3303
|
values (?, ?, ?, ?, ?, ?)
|
|
@@ -3129,7 +3326,7 @@ var definitions = {
|
|
|
3129
3326
|
`,
|
|
3130
3327
|
...fixedShard("data/ai_scores.jsonl", "ai_scores"),
|
|
3131
3328
|
merge: {
|
|
3132
|
-
order:
|
|
3329
|
+
order: 21,
|
|
3133
3330
|
sql: `
|
|
3134
3331
|
insert into ai_scores (
|
|
3135
3332
|
entity_kind, entity_id, model, score, summary, reasoning, updated_at
|
|
@@ -3317,7 +3514,7 @@ function adaptLegacyTweetState(schemaVersion, tweets, collections, timelineEdges
|
|
|
3317
3514
|
const nextTimelineEdges = [...timelineEdges];
|
|
3318
3515
|
const collectionKeys = new Set(collections.map((row) => `${String(row.account_id)}\0${String(row.tweet_id)}\0${String(row.kind)}`));
|
|
3319
3516
|
const edgeKeys = new Set(timelineEdges.map((row) => `${String(row.account_id)}\0${String(row.tweet_id)}\0${String(row.kind)}`));
|
|
3320
|
-
const edgeKinds = new Set([
|
|
3517
|
+
const edgeKinds = /* @__PURE__ */ new Set([
|
|
3321
3518
|
"home",
|
|
3322
3519
|
"mention",
|
|
3323
3520
|
"authored",
|
|
@@ -3491,6 +3688,8 @@ var ImportRepository = class {
|
|
|
3491
3688
|
}
|
|
3492
3689
|
clearBackupImport() {
|
|
3493
3690
|
this.db.exec(`
|
|
3691
|
+
delete from x_list_members;
|
|
3692
|
+
delete from x_lists;
|
|
3494
3693
|
delete from follow_events;
|
|
3495
3694
|
delete from follow_edges;
|
|
3496
3695
|
delete from follow_snapshot_members;
|
|
@@ -3797,10 +3996,11 @@ function runSubprocessEffect(options) {
|
|
|
3797
3996
|
}
|
|
3798
3997
|
//#endregion
|
|
3799
3998
|
//#region src/lib/backup.ts
|
|
3800
|
-
var BACKUP_SCHEMA_VERSION =
|
|
3999
|
+
var BACKUP_SCHEMA_VERSION = 3;
|
|
3801
4000
|
var MIN_SUPPORTED_BACKUP_SCHEMA_VERSION = 1;
|
|
3802
4001
|
var MANIFEST_PATH = "manifest.json";
|
|
3803
4002
|
var DATA_DIR = "data";
|
|
4003
|
+
var GITATTRIBUTES_PATH = ".gitattributes";
|
|
3804
4004
|
var AUTO_SYNC_CACHE_KEY = "backup:auto-sync";
|
|
3805
4005
|
var DEFAULT_STALE_AFTER_SECONDS = 900;
|
|
3806
4006
|
var autoUpdateInFlight = null;
|
|
@@ -3959,6 +4159,8 @@ data/moderation/mutes.jsonl
|
|
|
3959
4159
|
data/follow_snapshots.jsonl
|
|
3960
4160
|
data/follow_snapshot_members.jsonl
|
|
3961
4161
|
data/follow_edges.jsonl
|
|
4162
|
+
data/lists/lists.jsonl
|
|
4163
|
+
data/lists/members.jsonl
|
|
3962
4164
|
data/follow_events.jsonl
|
|
3963
4165
|
\`\`\`
|
|
3964
4166
|
|
|
@@ -3982,23 +4184,38 @@ function writeManifestEffect(repoPath, manifest) {
|
|
|
3982
4184
|
function readPreviousManifestEffect(repoPath) {
|
|
3983
4185
|
return readManifestEffect(repoPath).pipe(Effect.catchAll(() => Effect.succeed(void 0)));
|
|
3984
4186
|
}
|
|
4187
|
+
function ensureBackupGitattributesEffect(repoPath) {
|
|
4188
|
+
return Effect.gen(function* () {
|
|
4189
|
+
const attributesPath = yield* trySync$10(() => resolveBackupFilePath(repoPath, GITATTRIBUTES_PATH));
|
|
4190
|
+
yield* assertNoSymlinkAncestorEffect(repoPath, attributesPath);
|
|
4191
|
+
const generatedBlock = [
|
|
4192
|
+
"# BEGIN birdclaw backup attributes",
|
|
4193
|
+
"# Backup hashes use the raw LF-delimited bytes written by Birdclaw.",
|
|
4194
|
+
...["data/**/*.jsonl text eol=lf", `${MANIFEST_PATH} text eol=lf`],
|
|
4195
|
+
"# END birdclaw backup attributes",
|
|
4196
|
+
""
|
|
4197
|
+
].join("\n");
|
|
4198
|
+
const current = yield* tryPromise(() => fs.readFile(attributesPath, "utf8")).pipe(Effect.option);
|
|
4199
|
+
if (current._tag === "Some" && current.value.endsWith(generatedBlock)) return;
|
|
4200
|
+
const preserved = current._tag === "Some" ? current.value.replaceAll(generatedBlock, "").replace(/[\r\n]+$/u, "") : "";
|
|
4201
|
+
const content = preserved ? `${preserved}\n\n${generatedBlock}` : generatedBlock;
|
|
4202
|
+
yield* tryPromise(() => fs.writeFile(attributesPath, content, "utf8"));
|
|
4203
|
+
});
|
|
4204
|
+
}
|
|
3985
4205
|
function maybeCommitAndPushEffect({ repoPath, message, commit, push }) {
|
|
3986
4206
|
if (!commit && !push) return Effect.succeed(void 0);
|
|
3987
4207
|
return Effect.gen(function* () {
|
|
3988
|
-
yield* gitEffect([
|
|
3989
|
-
"-C",
|
|
3990
|
-
repoPath,
|
|
3991
|
-
"rev-parse",
|
|
3992
|
-
"--is-inside-work-tree"
|
|
3993
|
-
]).pipe(Effect.catchAll(() => gitEffect([
|
|
4208
|
+
if (!(yield* isGitRepoEffect(repoPath))) yield* gitEffect([
|
|
3994
4209
|
"-C",
|
|
3995
4210
|
repoPath,
|
|
3996
4211
|
"init"
|
|
3997
|
-
])
|
|
4212
|
+
]);
|
|
4213
|
+
if (!(yield* isGitRepoEffect(repoPath))) return yield* Effect.fail(/* @__PURE__ */ new Error("Backup Git operations must run at the configured repository root"));
|
|
3998
4214
|
yield* gitEffect([
|
|
3999
4215
|
"-C",
|
|
4000
4216
|
repoPath,
|
|
4001
4217
|
"add",
|
|
4218
|
+
GITATTRIBUTES_PATH,
|
|
4002
4219
|
"README.md",
|
|
4003
4220
|
MANIFEST_PATH,
|
|
4004
4221
|
DATA_DIR
|
|
@@ -4077,6 +4294,7 @@ function maybeCommitAndPushEffect({ repoPath, message, commit, push }) {
|
|
|
4077
4294
|
});
|
|
4078
4295
|
}
|
|
4079
4296
|
function isGitRepoEffect(repoPath) {
|
|
4297
|
+
if (!existsSync(path.join(repoPath, ".git"))) return Effect.succeed(false);
|
|
4080
4298
|
return gitEffect([
|
|
4081
4299
|
"-C",
|
|
4082
4300
|
repoPath,
|
|
@@ -4143,7 +4361,7 @@ function ensureBackupGitRepoEffect({ repoPath, remote }) {
|
|
|
4143
4361
|
repoPath,
|
|
4144
4362
|
"fetch",
|
|
4145
4363
|
"origin",
|
|
4146
|
-
"main"
|
|
4364
|
+
"main:refs/remotes/origin/main"
|
|
4147
4365
|
]).pipe(Effect.flatMap(() => gitEffect([
|
|
4148
4366
|
"-C",
|
|
4149
4367
|
repoPath,
|
|
@@ -4187,6 +4405,7 @@ function exportBackupEffect({ repoPath, db, commit = false, push = false, messag
|
|
|
4187
4405
|
yield* tryPromise(() => fs.mkdir(resolvedRepoPath, { recursive: true }));
|
|
4188
4406
|
const repoStat = yield* tryPromise(() => fs.lstat(resolvedRepoPath));
|
|
4189
4407
|
if (!repoStat.isDirectory() || repoStat.isSymbolicLink()) return yield* Effect.fail(/* @__PURE__ */ new Error("Backup repository path must be a real directory"));
|
|
4408
|
+
yield* ensureBackupGitattributesEffect(resolvedRepoPath);
|
|
4190
4409
|
yield* ensureBackupReadmeEffect(resolvedRepoPath);
|
|
4191
4410
|
const shards = yield* trySync$10(() => buildShards(database));
|
|
4192
4411
|
const shardEntries = yield* trySync$10(() => [...shards.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -4851,6 +5070,12 @@ function runBirdJsonCommandEffect$1(args, timeoutMs) {
|
|
|
4851
5070
|
});
|
|
4852
5071
|
}));
|
|
4853
5072
|
}
|
|
5073
|
+
function runBirdTweetJsonCommandEffect(args, timeoutMs) {
|
|
5074
|
+
return runBirdJsonCommandEffect$1([...args, "--json-full"], timeoutMs).pipe(Effect.catchAll((error) => {
|
|
5075
|
+
if (!isUnsupportedBirdOptionError(error, "--json-full")) return Effect.fail(error);
|
|
5076
|
+
return runBirdJsonCommandEffect$1([...args, "--json"], timeoutMs);
|
|
5077
|
+
}));
|
|
5078
|
+
}
|
|
4854
5079
|
function getBirdTweetItems(payload, command) {
|
|
4855
5080
|
if (Array.isArray(payload)) return payload;
|
|
4856
5081
|
if (payload && typeof payload === "object" && Array.isArray(payload.tweets)) return payload.tweets;
|
|
@@ -4901,15 +5126,48 @@ function toReferencedTweets(item) {
|
|
|
4901
5126
|
});
|
|
4902
5127
|
return references.length > 0 ? references : void 0;
|
|
4903
5128
|
}
|
|
5129
|
+
function getRecord(value) {
|
|
5130
|
+
return value && typeof value === "object" ? value : void 0;
|
|
5131
|
+
}
|
|
5132
|
+
function findGraphqlAvatarUrl(value, authorId) {
|
|
5133
|
+
const pending = [value];
|
|
5134
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5135
|
+
while (pending.length > 0) {
|
|
5136
|
+
const record = getRecord(pending.pop());
|
|
5137
|
+
if (!record || seen.has(record)) continue;
|
|
5138
|
+
seen.add(record);
|
|
5139
|
+
if (record.rest_id === authorId) {
|
|
5140
|
+
const avatar = getRecord(record.avatar);
|
|
5141
|
+
if (typeof avatar?.image_url === "string" && avatar.image_url) return avatar.image_url;
|
|
5142
|
+
}
|
|
5143
|
+
for (const child of Object.values(record)) if (child && typeof child === "object") pending.push(child);
|
|
5144
|
+
}
|
|
5145
|
+
}
|
|
5146
|
+
function isHydratedBirdTweetItem(value) {
|
|
5147
|
+
const item = value;
|
|
5148
|
+
return typeof item?.id === "string" && typeof item.text === "string" && typeof item.createdAt === "string";
|
|
5149
|
+
}
|
|
4904
5150
|
function normalizeBirdTweets(items) {
|
|
4905
5151
|
const users = /* @__PURE__ */ new Map();
|
|
4906
|
-
const
|
|
5152
|
+
const includedTweets = /* @__PURE__ */ new Map();
|
|
5153
|
+
const normalizeItem = (item, preserveMissingMetrics = false) => {
|
|
4907
5154
|
const authorId = String(item.authorId ?? item.author?.username ?? "unknown");
|
|
4908
|
-
|
|
5155
|
+
const profileImageUrl = item.author?.profileImageUrl ?? findGraphqlAvatarUrl(item._raw, authorId);
|
|
5156
|
+
const existingUser = users.get(authorId);
|
|
5157
|
+
if (!existingUser) users.set(authorId, {
|
|
4909
5158
|
id: authorId,
|
|
4910
5159
|
username: item.author?.username ?? `user_${authorId}`,
|
|
4911
|
-
name: item.author?.name ?? item.author?.username ?? `user_${authorId}
|
|
5160
|
+
name: item.author?.name ?? item.author?.username ?? `user_${authorId}`,
|
|
5161
|
+
...profileImageUrl ? { profile_image_url: profileImageUrl } : {}
|
|
5162
|
+
});
|
|
5163
|
+
else if (profileImageUrl && !existingUser.profile_image_url) users.set(authorId, {
|
|
5164
|
+
...existingUser,
|
|
5165
|
+
profile_image_url: profileImageUrl
|
|
4912
5166
|
});
|
|
5167
|
+
if (isHydratedBirdTweetItem(item.quotedTweet)) {
|
|
5168
|
+
const quotedTweet = normalizeItem(item.quotedTweet, true);
|
|
5169
|
+
if (quotedTweet.id !== item.id) includedTweets.set(quotedTweet.id, quotedTweet);
|
|
5170
|
+
}
|
|
4913
5171
|
return {
|
|
4914
5172
|
id: item.id,
|
|
4915
5173
|
author_id: authorId,
|
|
@@ -4918,17 +5176,25 @@ function normalizeBirdTweets(items) {
|
|
|
4918
5176
|
conversation_id: item.conversationId ?? item.id,
|
|
4919
5177
|
entities: toTweetEntities(item),
|
|
4920
5178
|
referenced_tweets: toReferencedTweets(item),
|
|
4921
|
-
public_metrics: {
|
|
5179
|
+
public_metrics: preserveMissingMetrics ? {
|
|
5180
|
+
...item.replyCount === void 0 ? {} : { reply_count: Number(item.replyCount) },
|
|
5181
|
+
...item.retweetCount === void 0 ? {} : { retweet_count: Number(item.retweetCount) },
|
|
5182
|
+
...item.likeCount === void 0 ? {} : { like_count: Number(item.likeCount) }
|
|
5183
|
+
} : {
|
|
4922
5184
|
reply_count: Number(item.replyCount ?? 0),
|
|
4923
5185
|
retweet_count: Number(item.retweetCount ?? 0),
|
|
4924
5186
|
like_count: Number(item.likeCount ?? 0)
|
|
4925
5187
|
},
|
|
4926
5188
|
edit_history_tweet_ids: [item.id]
|
|
4927
5189
|
};
|
|
4928
|
-
}
|
|
5190
|
+
};
|
|
5191
|
+
const data = items.map((item) => normalizeItem(item));
|
|
5192
|
+
const includes = {};
|
|
5193
|
+
if (users.size > 0) includes.users = Array.from(users.values());
|
|
5194
|
+
if (includedTweets.size > 0) includes.tweets = Array.from(includedTweets.values());
|
|
4929
5195
|
return {
|
|
4930
5196
|
data,
|
|
4931
|
-
includes: users.size > 0
|
|
5197
|
+
includes: users.size > 0 || includedTweets.size > 0 ? includes : void 0,
|
|
4932
5198
|
meta: {
|
|
4933
5199
|
result_count: data.length,
|
|
4934
5200
|
page_count: 1,
|
|
@@ -4952,11 +5218,10 @@ function normalizeBirdTweetsPayloadEffect(payload, command) {
|
|
|
4952
5218
|
}
|
|
4953
5219
|
function listMentionsViaBirdEffect({ maxResults }) {
|
|
4954
5220
|
return Effect.gen(function* () {
|
|
4955
|
-
return yield* normalizeBirdTweetsPayloadEffect(yield* parseBirdJsonEffect(yield*
|
|
5221
|
+
return yield* normalizeBirdTweetsPayloadEffect(yield* parseBirdJsonEffect(yield* runBirdTweetJsonCommandEffect([
|
|
4956
5222
|
"mentions",
|
|
4957
5223
|
"-n",
|
|
4958
|
-
String(maxResults)
|
|
4959
|
-
"--json"
|
|
5224
|
+
String(maxResults)
|
|
4960
5225
|
])), "mentions");
|
|
4961
5226
|
});
|
|
4962
5227
|
}
|
|
@@ -4965,12 +5230,13 @@ function listTweetsViaBirdCommandEffect({ command, maxResults, all, maxPages })
|
|
|
4965
5230
|
const args = [
|
|
4966
5231
|
command,
|
|
4967
5232
|
"-n",
|
|
4968
|
-
String(maxResults)
|
|
4969
|
-
"--json"
|
|
5233
|
+
String(maxResults)
|
|
4970
5234
|
];
|
|
4971
|
-
if (all)
|
|
4972
|
-
|
|
4973
|
-
|
|
5235
|
+
if (all) {
|
|
5236
|
+
args.push("--all");
|
|
5237
|
+
if (maxPages !== void 0) args.push("--max-pages", String(maxPages));
|
|
5238
|
+
}
|
|
5239
|
+
return yield* normalizeBirdTweetsPayloadEffect(yield* parseBirdJsonEffect(yield* runBirdTweetJsonCommandEffect(args)), command);
|
|
4974
5240
|
});
|
|
4975
5241
|
}
|
|
4976
5242
|
function listLikedTweetsViaBirdEffect(options) {
|
|
@@ -4991,12 +5257,11 @@ function searchTweetsViaBirdEffect(query, options) {
|
|
|
4991
5257
|
"search",
|
|
4992
5258
|
query,
|
|
4993
5259
|
"-n",
|
|
4994
|
-
String(options.maxResults)
|
|
4995
|
-
"--json"
|
|
5260
|
+
String(options.maxResults)
|
|
4996
5261
|
];
|
|
4997
5262
|
if (options.all) args.push("--all");
|
|
4998
5263
|
if (options.all && options.maxPages !== void 0) args.push("--max-pages", String(options.maxPages));
|
|
4999
|
-
return yield* normalizeBirdTweetsPayloadEffect(yield* parseBirdJsonEffect(yield*
|
|
5264
|
+
return yield* normalizeBirdTweetsPayloadEffect(yield* parseBirdJsonEffect(yield* runBirdTweetJsonCommandEffect(args)), "search");
|
|
5000
5265
|
});
|
|
5001
5266
|
}
|
|
5002
5267
|
function listHomeTimelineViaBirdEffect({ maxResults, following = true }) {
|
|
@@ -5004,11 +5269,10 @@ function listHomeTimelineViaBirdEffect({ maxResults, following = true }) {
|
|
|
5004
5269
|
const args = [
|
|
5005
5270
|
"home",
|
|
5006
5271
|
"-n",
|
|
5007
|
-
String(maxResults)
|
|
5008
|
-
"--json"
|
|
5272
|
+
String(maxResults)
|
|
5009
5273
|
];
|
|
5010
5274
|
if (following) args.push("--following");
|
|
5011
|
-
return yield* normalizeBirdTweetsPayloadEffect(yield* parseBirdJsonEffect(yield*
|
|
5275
|
+
return yield* normalizeBirdTweetsPayloadEffect(yield* parseBirdJsonEffect(yield* runBirdTweetJsonCommandEffect(args)), "home");
|
|
5012
5276
|
});
|
|
5013
5277
|
}
|
|
5014
5278
|
function normalizeBirdFollowUsers(payload, command, maxResults) {
|
|
@@ -5017,12 +5281,14 @@ function normalizeBirdFollowUsers(payload, command, maxResults) {
|
|
|
5017
5281
|
if (!Array.isArray(users)) throw new Error(`bird ${command} returned unexpected JSON`);
|
|
5018
5282
|
const data = users.map(toXurlMentionUser).filter((user) => Boolean(user));
|
|
5019
5283
|
const nextToken = !Array.isArray(rawPayload) && typeof rawPayload.nextCursor === "string" ? rawPayload.nextCursor : null;
|
|
5284
|
+
const paginationKnownComplete = Array.isArray(rawPayload) ? data.length < maxResults : nextToken === null;
|
|
5020
5285
|
return {
|
|
5021
5286
|
data,
|
|
5022
5287
|
meta: {
|
|
5023
5288
|
result_count: data.length,
|
|
5024
5289
|
page_count: data.length > 0 ? Math.max(1, Math.ceil(data.length / maxResults)) : 1,
|
|
5025
|
-
next_token: nextToken
|
|
5290
|
+
next_token: nextToken,
|
|
5291
|
+
pagination_known_complete: paginationKnownComplete
|
|
5026
5292
|
}
|
|
5027
5293
|
};
|
|
5028
5294
|
}
|
|
@@ -5048,14 +5314,10 @@ function listFollowUsersViaBirdEffect({ direction, userId, maxResults, all, maxP
|
|
|
5048
5314
|
}
|
|
5049
5315
|
function listThreadViaBirdEffect({ tweetId, all, maxPages, timeoutMs }) {
|
|
5050
5316
|
return Effect.gen(function* () {
|
|
5051
|
-
const args = [
|
|
5052
|
-
"thread",
|
|
5053
|
-
tweetId,
|
|
5054
|
-
"--json"
|
|
5055
|
-
];
|
|
5317
|
+
const args = ["thread", tweetId];
|
|
5056
5318
|
if (all) args.push("--all");
|
|
5057
5319
|
if (maxPages !== void 0) args.push("--max-pages", String(maxPages));
|
|
5058
|
-
return yield* normalizeBirdTweetsPayloadEffect(yield* parseBirdJsonEffect(yield*
|
|
5320
|
+
return yield* normalizeBirdTweetsPayloadEffect(yield* parseBirdJsonEffect(yield* runBirdTweetJsonCommandEffect(args, timeoutMs)), "thread");
|
|
5059
5321
|
});
|
|
5060
5322
|
}
|
|
5061
5323
|
function normalizeBirdDmsPayloadEffect(payload) {
|
|
@@ -5514,7 +5776,7 @@ function runOAuth2JsonCommandEffect({ args, username, options, useConfiguredCand
|
|
|
5514
5776
|
return yield* runJsonCommandEffect(oauth2ArgsForCandidate(authCandidate, args), scopedOptions).pipe(Effect.catchAll((error) => {
|
|
5515
5777
|
if (!primaryUsername) return Effect.fail(error);
|
|
5516
5778
|
oauth2CandidateCache.delete(primaryUsername.toLowerCase());
|
|
5517
|
-
const attempted = new Set([authCandidate ? `${authCandidate.app ?? "default"}:${authCandidate.username}` : `default:${primaryUsername}`]);
|
|
5779
|
+
const attempted = /* @__PURE__ */ new Set([authCandidate ? `${authCandidate.app ?? "default"}:${authCandidate.username}` : `default:${primaryUsername}`]);
|
|
5518
5780
|
if (authCandidate?.username !== primaryUsername) attempted.add(`default:${primaryUsername}`);
|
|
5519
5781
|
return lookupOAuth2UsernameForAccountEffect(primaryUsername, attempted, deadlineMs).pipe(Effect.flatMap((fallbackUsername) => {
|
|
5520
5782
|
if (!fallbackUsername) return Effect.fail(error);
|
|
@@ -6150,8 +6412,8 @@ var MAX_AVATAR_BYTES = 2 * 1024 * 1024;
|
|
|
6150
6412
|
var MAX_AVATAR_DATA_URL_CHARS = MAX_AVATAR_BYTES * 4;
|
|
6151
6413
|
var REMOTE_AVATAR_TIMEOUT_MS = 1e4;
|
|
6152
6414
|
var DEFAULT_AVATAR_PREFETCH_CONCURRENCY = 8;
|
|
6153
|
-
var ALLOWED_REMOTE_AVATAR_HOSTS = new Set(["pbs.twimg.com"]);
|
|
6154
|
-
var RASTER_CONTENT_TYPES = new Set([
|
|
6415
|
+
var ALLOWED_REMOTE_AVATAR_HOSTS = /* @__PURE__ */ new Set(["pbs.twimg.com"]);
|
|
6416
|
+
var RASTER_CONTENT_TYPES = /* @__PURE__ */ new Set([
|
|
6155
6417
|
"image/jpeg",
|
|
6156
6418
|
"image/png",
|
|
6157
6419
|
"image/webp",
|
|
@@ -7747,6 +8009,12 @@ function buildMediaJsonFromIncludes(tweet, media = []) {
|
|
|
7747
8009
|
function getReferencedTweetId(tweet, type) {
|
|
7748
8010
|
return tweet.referenced_tweets?.find((item) => item.type === type)?.id ?? null;
|
|
7749
8011
|
}
|
|
8012
|
+
function toCanonicalTweets(payload) {
|
|
8013
|
+
const tweetsById = /* @__PURE__ */ new Map();
|
|
8014
|
+
for (const tweet of payload.includes?.tweets ?? []) tweetsById.set(tweet.id, tweet);
|
|
8015
|
+
for (const tweet of payload.data) tweetsById.set(tweet.id, tweet);
|
|
8016
|
+
return tweetsById.values();
|
|
8017
|
+
}
|
|
7750
8018
|
function replaceTweetFts$1(db, tweetId, text) {
|
|
7751
8019
|
db.prepare("delete from tweets_fts where tweet_id = ?").run(tweetId);
|
|
7752
8020
|
db.prepare("insert into tweets_fts (tweet_id, text) values (?, ?)").run(tweetId, text);
|
|
@@ -7764,7 +8032,7 @@ function ingestTweetPayload(db, { accountId, payload, source, edgeKind, collecti
|
|
|
7764
8032
|
created_at = excluded.created_at,
|
|
7765
8033
|
is_replied = max(tweets.is_replied, excluded.is_replied),
|
|
7766
8034
|
reply_to_id = coalesce(tweets.reply_to_id, excluded.reply_to_id),
|
|
7767
|
-
like_count = excluded.like_count,
|
|
8035
|
+
like_count = case when ? then tweets.like_count else excluded.like_count end,
|
|
7768
8036
|
media_count = max(tweets.media_count, excluded.media_count),
|
|
7769
8037
|
entities_json = excluded.entities_json,
|
|
7770
8038
|
media_json = case
|
|
@@ -7785,13 +8053,16 @@ function ingestTweetPayload(db, { accountId, payload, source, edgeKind, collecti
|
|
|
7785
8053
|
const tweetIds = [];
|
|
7786
8054
|
db.transaction(() => {
|
|
7787
8055
|
const observedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7788
|
-
|
|
8056
|
+
const primaryTweetIds = new Set(payload.data.map((tweet) => tweet.id));
|
|
8057
|
+
for (const tweet of toCanonicalTweets(payload)) {
|
|
8058
|
+
const isPrimaryTweet = primaryTweetIds.has(tweet.id);
|
|
7789
8059
|
const author = usersById.get(tweet.author_id);
|
|
7790
8060
|
const profile = author ? upsertProfileFromXUser(db, author) : ensureStubProfileForXUser(db, tweet.author_id);
|
|
7791
8061
|
const replyToId = getReferencedTweetId(tweet, "replied_to");
|
|
7792
8062
|
const quotedTweetId = getReferencedTweetId(tweet, "quoted");
|
|
7793
|
-
|
|
7794
|
-
|
|
8063
|
+
const shouldMarkReplied = isPrimaryTweet && markRepliesAsReplied && Boolean(replyToId);
|
|
8064
|
+
upsertTweet.run(tweet.id, profile.profile.id, tweet.text, tweet.created_at, shouldMarkReplied ? 1 : 0, replyToId, Number(tweet.public_metrics?.like_count ?? 0), countTweetMedia(tweet), JSON.stringify(tweetEntitiesFromXurl(tweet.entities)), buildMediaJsonFromIncludes(tweet, payload.includes?.media), quotedTweetId, !isPrimaryTweet && tweet.public_metrics?.like_count === void 0 ? 1 : 0);
|
|
8065
|
+
if (edgeKind && isPrimaryTweet) upsertTweetAccountEdge(db, {
|
|
7795
8066
|
accountId,
|
|
7796
8067
|
tweetId: tweet.id,
|
|
7797
8068
|
kind: edgeKind,
|
|
@@ -7799,9 +8070,9 @@ function ingestTweetPayload(db, { accountId, payload, source, edgeKind, collecti
|
|
|
7799
8070
|
seenAt: observedAt,
|
|
7800
8071
|
rawJson: JSON.stringify(tweet)
|
|
7801
8072
|
});
|
|
7802
|
-
upsertCollection?.run(accountId, tweet.id, collectionKind, source, JSON.stringify(tweet), observedAt);
|
|
8073
|
+
if (isPrimaryTweet) upsertCollection?.run(accountId, tweet.id, collectionKind, source, JSON.stringify(tweet), observedAt);
|
|
7803
8074
|
replaceTweetFts$1(db, tweet.id, tweet.text);
|
|
7804
|
-
tweetIds.push(tweet.id);
|
|
8075
|
+
if (isPrimaryTweet) tweetIds.push(tweet.id);
|
|
7805
8076
|
}
|
|
7806
8077
|
})();
|
|
7807
8078
|
return tweetIds;
|
|
@@ -7940,7 +8211,7 @@ function listMentionsByIds(db, accountId, tweetIds, limit) {
|
|
|
7940
8211
|
}
|
|
7941
8212
|
function mergeMentionThreadIntoLocalStore({ db, accountId, accountHandle, mentionIds, payload, source = "bird", writeThreadContextEdges = false }) {
|
|
7942
8213
|
const usersById = new Map((payload.includes?.users ?? []).map((user) => [user.id, user]));
|
|
7943
|
-
const groups = new Map([
|
|
8214
|
+
const groups = /* @__PURE__ */ new Map([
|
|
7944
8215
|
["mention", []],
|
|
7945
8216
|
["home", []],
|
|
7946
8217
|
["thread", []]
|
|
@@ -8001,7 +8272,7 @@ function fetchParentChainViaXurlEffect({ mention, maxDepth, timeoutMs, deadlineM
|
|
|
8001
8272
|
return Effect.gen(function* () {
|
|
8002
8273
|
const pages = [];
|
|
8003
8274
|
const warnings = [];
|
|
8004
|
-
const seenTweetIds = new Set([mention.id]);
|
|
8275
|
+
const seenTweetIds = /* @__PURE__ */ new Set([mention.id]);
|
|
8005
8276
|
let nextParentId = mention.replyToId;
|
|
8006
8277
|
let fallbackDepth = 0;
|
|
8007
8278
|
let generalReadTweets = 0;
|
|
@@ -8047,7 +8318,7 @@ function fetchParentChainViaXurlEffect({ mention, maxDepth, timeoutMs, deadlineM
|
|
|
8047
8318
|
}
|
|
8048
8319
|
function findMissingAncestorId(mention, payload) {
|
|
8049
8320
|
const tweetsById = new Map(payload.data.map((tweet) => [tweet.id, tweet]));
|
|
8050
|
-
const seenTweetIds = new Set([mention.id]);
|
|
8321
|
+
const seenTweetIds = /* @__PURE__ */ new Set([mention.id]);
|
|
8051
8322
|
const anchorTweet = tweetsById.get(mention.id) ?? mention.rawTweet;
|
|
8052
8323
|
let nextParentId = anchorTweet ? getReplyToId(anchorTweet) : mention.replyToId;
|
|
8053
8324
|
while (nextParentId) {
|
|
@@ -8539,7 +8810,7 @@ function getTimelineQualityReason(row, lowQualityThreshold) {
|
|
|
8539
8810
|
return "keep:long-text";
|
|
8540
8811
|
}
|
|
8541
8812
|
var RECENT_TIMELINE_EDGE_CANDIDATES = 5e3;
|
|
8542
|
-
function listTimelineItems({ resource, account, search, replyFilter = "all", since, until, untilId, includeReplies = true, qualityFilter = "all", lowQualityThreshold, includeQualityReason = false, likedOnly = false, bookmarkedOnly = false, limit = 18 }) {
|
|
8813
|
+
function listTimelineItems({ resource, account, listAccountId, listId, search, replyFilter = "all", since, until, untilId, includeReplies = true, qualityFilter = "all", lowQualityThreshold, includeQualityReason = false, likedOnly = false, bookmarkedOnly = false, limit = 18 }) {
|
|
8543
8814
|
const db = getReadDb();
|
|
8544
8815
|
const kind = resource === "mentions" ? "mention" : resource;
|
|
8545
8816
|
const params = [];
|
|
@@ -8557,13 +8828,13 @@ function listTimelineItems({ resource, account, search, replyFilter = "all", sin
|
|
|
8557
8828
|
let join = "";
|
|
8558
8829
|
let where = "where e.kind = ?";
|
|
8559
8830
|
let searchSnippetSelect = "";
|
|
8560
|
-
const canUseRecentEdgeWindow = !likedOnly && !bookmarkedOnly && !account && !search?.trim() && replyFilter === "all" && !since?.trim() && !until?.trim() && includeReplies && qualityFilter === "all";
|
|
8831
|
+
const canUseRecentEdgeWindow = !likedOnly && !bookmarkedOnly && !listId && !account && !search?.trim() && replyFilter === "all" && !since?.trim() && !until?.trim() && includeReplies && qualityFilter === "all";
|
|
8561
8832
|
if (likedOnly || bookmarkedOnly) {
|
|
8562
8833
|
if (likedOnly && bookmarkedOnly) timelineEdgesCte = `
|
|
8563
8834
|
with timeline_edges as (
|
|
8564
8835
|
select likes.account_id, likes.tweet_id, 'home' as kind, likes.raw_json
|
|
8565
|
-
|
|
8566
|
-
join tweet_collections bookmarks
|
|
8836
|
+
from tweet_collections likes indexed by idx_tweet_collections_tweet
|
|
8837
|
+
join tweet_collections bookmarks indexed by idx_tweet_collections_tweet
|
|
8567
8838
|
on bookmarks.account_id = likes.account_id
|
|
8568
8839
|
and bookmarks.tweet_id = likes.tweet_id
|
|
8569
8840
|
and bookmarks.kind = 'bookmarks'
|
|
@@ -8575,7 +8846,7 @@ function listTimelineItems({ resource, account, search, replyFilter = "all", sin
|
|
|
8575
8846
|
timelineEdgesCte = `
|
|
8576
8847
|
with timeline_edges as (
|
|
8577
8848
|
select account_id, tweet_id, 'home' as kind, raw_json
|
|
8578
|
-
from tweet_collections
|
|
8849
|
+
from tweet_collections indexed by idx_tweet_collections_tweet
|
|
8579
8850
|
where kind = ?
|
|
8580
8851
|
)
|
|
8581
8852
|
`;
|
|
@@ -8637,6 +8908,20 @@ function listTimelineItems({ resource, account, search, replyFilter = "all", sin
|
|
|
8637
8908
|
where += " and t.created_at < ?";
|
|
8638
8909
|
params.push(until.trim());
|
|
8639
8910
|
}
|
|
8911
|
+
if (Boolean(listId) !== Boolean(listAccountId)) throw new Error("List filtering requires both listId and listAccountId");
|
|
8912
|
+
if (listId && listAccountId) {
|
|
8913
|
+
where += `
|
|
8914
|
+
and exists (
|
|
8915
|
+
select 1
|
|
8916
|
+
from x_list_members list_member
|
|
8917
|
+
where list_member.account_id = ?
|
|
8918
|
+
and list_member.list_id = ?
|
|
8919
|
+
and list_member.profile_id = t.author_profile_id
|
|
8920
|
+
and list_member.current = 1
|
|
8921
|
+
)
|
|
8922
|
+
`;
|
|
8923
|
+
params.push(listAccountId, listId);
|
|
8924
|
+
}
|
|
8640
8925
|
const ftsSearch = search?.trim() ? toFtsSearchQuery(search) : "";
|
|
8641
8926
|
if (ftsSearch) {
|
|
8642
8927
|
join += " join tweets_fts on tweets_fts.tweet_id = t.id ";
|
|
@@ -9654,6 +9939,7 @@ function reachedStartTimeBoundary(payload, startTimeMs) {
|
|
|
9654
9939
|
function mergeTimelinePayloads(payloads, limit) {
|
|
9655
9940
|
const data = [];
|
|
9656
9941
|
const usersById = /* @__PURE__ */ new Map();
|
|
9942
|
+
const tweetsById = /* @__PURE__ */ new Map();
|
|
9657
9943
|
const mediaByKey = /* @__PURE__ */ new Map();
|
|
9658
9944
|
let meta;
|
|
9659
9945
|
for (const payload of payloads) {
|
|
@@ -9664,6 +9950,7 @@ function mergeTimelinePayloads(payloads, limit) {
|
|
|
9664
9950
|
if (data.length >= limit) break;
|
|
9665
9951
|
}
|
|
9666
9952
|
for (const user of payload.includes?.users ?? []) usersById.set(user.id, user);
|
|
9953
|
+
for (const tweet of payload.includes?.tweets ?? []) tweetsById.set(tweet.id, tweet);
|
|
9667
9954
|
for (const media of payload.includes?.media ?? []) mediaByKey.set(media.media_key, media);
|
|
9668
9955
|
if (data.length >= limit) break;
|
|
9669
9956
|
}
|
|
@@ -9671,6 +9958,7 @@ function mergeTimelinePayloads(payloads, limit) {
|
|
|
9671
9958
|
data,
|
|
9672
9959
|
includes: {
|
|
9673
9960
|
users: [...usersById.values()],
|
|
9961
|
+
tweets: [...tweetsById.values()],
|
|
9674
9962
|
media: [...mediaByKey.values()]
|
|
9675
9963
|
},
|
|
9676
9964
|
meta
|
|
@@ -11119,7 +11407,7 @@ function runModeEffect(mode, options) {
|
|
|
11119
11407
|
}
|
|
11120
11408
|
function combineTweetSearchResults(left, right, limit) {
|
|
11121
11409
|
if (left.ok && right.ok) {
|
|
11122
|
-
const tweetIds = [
|
|
11410
|
+
const tweetIds = [.../* @__PURE__ */ new Set([...left.tweetIds, ...right.tweetIds])].slice(0, limit);
|
|
11123
11411
|
const liveSources = new Set([left.source, right.source].filter((source) => source !== "cache"));
|
|
11124
11412
|
return {
|
|
11125
11413
|
ok: true,
|
|
@@ -11257,6 +11545,8 @@ function compactTweet$1(source, item) {
|
|
|
11257
11545
|
authorProfile: item.author,
|
|
11258
11546
|
createdAt: item.createdAt,
|
|
11259
11547
|
text: item.text,
|
|
11548
|
+
entities: item.entities,
|
|
11549
|
+
media: item.media,
|
|
11260
11550
|
likeCount: item.likeCount,
|
|
11261
11551
|
liked: item.liked,
|
|
11262
11552
|
bookmarked: item.bookmarked,
|
|
@@ -11291,7 +11581,9 @@ function collectLiveSearchTweets(options) {
|
|
|
11291
11581
|
t.created_at,
|
|
11292
11582
|
t.is_replied,
|
|
11293
11583
|
t.like_count,
|
|
11294
|
-
|
|
11584
|
+
t.media_count,
|
|
11585
|
+
t.entities_json,
|
|
11586
|
+
t.media_json,
|
|
11295
11587
|
case
|
|
11296
11588
|
when exists (
|
|
11297
11589
|
select 1 from tweet_collections collection
|
|
@@ -11341,6 +11633,8 @@ function collectLiveSearchTweets(options) {
|
|
|
11341
11633
|
},
|
|
11342
11634
|
createdAt: String(row.created_at),
|
|
11343
11635
|
text: String(row.text),
|
|
11636
|
+
entities: parseJsonField$1(row.entities_json, {}),
|
|
11637
|
+
media: parseJsonField$1(row.media_json, []),
|
|
11344
11638
|
likeCount: Number(row.like_count),
|
|
11345
11639
|
liked: Boolean(row.liked),
|
|
11346
11640
|
bookmarked: Boolean(row.bookmarked),
|
|
@@ -12720,7 +13014,7 @@ var Route$11 = createFileRoute("/api/profile-analysis")({ server: { handlers: {
|
|
|
12720
13014
|
//#region src/lib/link-insights.ts
|
|
12721
13015
|
var DEFAULT_LIMIT$1 = 30;
|
|
12722
13016
|
var DEFAULT_COMMENTS_LIMIT = 8;
|
|
12723
|
-
var TRACKING_PARAMS = new Set([
|
|
13017
|
+
var TRACKING_PARAMS = /* @__PURE__ */ new Set([
|
|
12724
13018
|
"fbclid",
|
|
12725
13019
|
"gclid",
|
|
12726
13020
|
"igshid",
|
|
@@ -12739,17 +13033,17 @@ var VIDEO_HOST_SUFFIXES = [
|
|
|
12739
13033
|
"tiktok.com",
|
|
12740
13034
|
"loom.com"
|
|
12741
13035
|
];
|
|
12742
|
-
var VIDEO_EXACT_HOSTS = new Set([
|
|
13036
|
+
var VIDEO_EXACT_HOSTS = /* @__PURE__ */ new Set([
|
|
12743
13037
|
"youtu.be",
|
|
12744
13038
|
"clips.twitch.tv",
|
|
12745
13039
|
"vm.tiktok.com"
|
|
12746
13040
|
]);
|
|
12747
13041
|
var EXCLUDED_HOST_SUFFIXES = ["x.com", "twitter.com"];
|
|
12748
|
-
var EXCLUDED_EXACT_HOSTS = new Set(["t.co"]);
|
|
13042
|
+
var EXCLUDED_EXACT_HOSTS = /* @__PURE__ */ new Set(["t.co"]);
|
|
12749
13043
|
var URL_WHITESPACE = /\s+/g;
|
|
12750
13044
|
var RAW_URL_PATTERN = /https?:\/\/[^\s<>"'`]+/g;
|
|
12751
13045
|
var SQL_URL_EXPRESSION = "lower(coalesce(nullif(e.final_url, ''), nullif(e.expanded_url, ''), e.short_url))";
|
|
12752
|
-
var TITLE_SEGMENT_STOPWORDS = new Set([
|
|
13046
|
+
var TITLE_SEGMENT_STOPWORDS = /* @__PURE__ */ new Set([
|
|
12753
13047
|
"a",
|
|
12754
13048
|
"about",
|
|
12755
13049
|
"blog",
|
|
@@ -12762,7 +13056,7 @@ var TITLE_SEGMENT_STOPWORDS = new Set([
|
|
|
12762
13056
|
"story",
|
|
12763
13057
|
"watch"
|
|
12764
13058
|
]);
|
|
12765
|
-
var TITLE_WORD_OVERRIDES = new Map([
|
|
13059
|
+
var TITLE_WORD_OVERRIDES = /* @__PURE__ */ new Map([
|
|
12766
13060
|
["ai", "AI"],
|
|
12767
13061
|
["api", "API"],
|
|
12768
13062
|
["codex", "Codex"],
|
|
@@ -12773,7 +13067,7 @@ var TITLE_WORD_OVERRIDES = new Map([
|
|
|
12773
13067
|
["url", "URL"],
|
|
12774
13068
|
["wwdc", "WWDC"]
|
|
12775
13069
|
]);
|
|
12776
|
-
var TITLE_SMALL_WORDS = new Set([
|
|
13070
|
+
var TITLE_SMALL_WORDS = /* @__PURE__ */ new Set([
|
|
12777
13071
|
"a",
|
|
12778
13072
|
"an",
|
|
12779
13073
|
"and",
|
|
@@ -12922,7 +13216,7 @@ function stripUrls(text, urls) {
|
|
|
12922
13216
|
let output = text;
|
|
12923
13217
|
for (const url of urls) {
|
|
12924
13218
|
if (!url) continue;
|
|
12925
|
-
const variants = new Set([url]);
|
|
13219
|
+
const variants = /* @__PURE__ */ new Set([url]);
|
|
12926
13220
|
try {
|
|
12927
13221
|
const parsed = new URL(url);
|
|
12928
13222
|
variants.add(parsed.href);
|
|
@@ -13309,9 +13603,9 @@ function getLinkInsights(query = {}) {
|
|
|
13309
13603
|
kind: rowKind,
|
|
13310
13604
|
lastSeenAt: row.created_at,
|
|
13311
13605
|
mentions: [mention],
|
|
13312
|
-
seenMentions: new Set([mention.id]),
|
|
13313
|
-
sharers: new Set([sharerKey]),
|
|
13314
|
-
sharerProfiles: mention.sharedBy ? new Map([[mention.sharedBy.id, mention.sharedBy]]) : /* @__PURE__ */ new Map(),
|
|
13606
|
+
seenMentions: /* @__PURE__ */ new Set([mention.id]),
|
|
13607
|
+
sharers: /* @__PURE__ */ new Set([sharerKey]),
|
|
13608
|
+
sharerProfiles: mention.sharedBy ? /* @__PURE__ */ new Map([[mention.sharedBy.id, mention.sharedBy]]) : /* @__PURE__ */ new Map(),
|
|
13315
13609
|
shareCount: 1,
|
|
13316
13610
|
title: row.title,
|
|
13317
13611
|
topSharer: mention.sharedBy ?? null,
|
|
@@ -13487,6 +13781,7 @@ function compactTweet(source, item) {
|
|
|
13487
13781
|
createdAt: item.createdAt,
|
|
13488
13782
|
text: item.text,
|
|
13489
13783
|
entities: item.entities,
|
|
13784
|
+
media: item.media,
|
|
13490
13785
|
likeCount: item.likeCount,
|
|
13491
13786
|
liked: item.liked,
|
|
13492
13787
|
bookmarked: item.bookmarked,
|
|
@@ -13506,6 +13801,7 @@ function compactEmbeddedTweet(item) {
|
|
|
13506
13801
|
createdAt: item.createdAt,
|
|
13507
13802
|
text: item.text,
|
|
13508
13803
|
entities: item.entities,
|
|
13804
|
+
media: item.media,
|
|
13509
13805
|
likeCount: item.likeCount ?? 0,
|
|
13510
13806
|
liked: Boolean(item.liked),
|
|
13511
13807
|
bookmarked: Boolean(item.bookmarked),
|
|
@@ -14175,7 +14471,7 @@ var LETTER_PATTERN = /[a-z]/i;
|
|
|
14175
14471
|
var URL_OR_HANDLE_PATTERN = /(https?:\/\/|@|[A-Z0-9._%+-]+@[A-Z0-9.-]+)/i;
|
|
14176
14472
|
var MONEY_PATTERN = /\$\s?\d/;
|
|
14177
14473
|
var EMOJI_BLOCK_PATTERN = /[\u{1f1e6}-\u{1f1ff}\u{1f300}-\u{1faff}\u{2600}-\u{27bf}]/gu;
|
|
14178
|
-
var GENERIC_LOCATIONS = new Set([
|
|
14474
|
+
var GENERIC_LOCATIONS = /* @__PURE__ */ new Set([
|
|
14179
14475
|
"earth",
|
|
14180
14476
|
"everywhere",
|
|
14181
14477
|
"internet",
|
|
@@ -14193,7 +14489,7 @@ var GENERIC_LOCATIONS = new Set([
|
|
|
14193
14489
|
"world",
|
|
14194
14490
|
"world wide web"
|
|
14195
14491
|
]);
|
|
14196
|
-
var DROP_WORDS = new Set([
|
|
14492
|
+
var DROP_WORDS = /* @__PURE__ */ new Set([
|
|
14197
14493
|
"a",
|
|
14198
14494
|
"an",
|
|
14199
14495
|
"and",
|
|
@@ -14747,7 +15043,7 @@ function upsertUrlExpansion(db, item) {
|
|
|
14747
15043
|
var FETCH_TIMEOUT_MS = 12e3;
|
|
14748
15044
|
var MAX_HTML_CHARS = 2e6;
|
|
14749
15045
|
var MAX_REDIRECTS = 4;
|
|
14750
|
-
var NO_BODY_STATUS_CODES = new Set([
|
|
15046
|
+
var NO_BODY_STATUS_CODES = /* @__PURE__ */ new Set([
|
|
14751
15047
|
204,
|
|
14752
15048
|
205,
|
|
14753
15049
|
304
|