birdclaw 0.8.5 → 0.9.1
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 +34 -0
- package/README.md +23 -3
- package/dist/cli/birdclaw.js +1344 -190
- package/dist/client/assets/{AvatarChip-DLoqM5xD.js → AvatarChip-BN3b4MhM.js} +1 -1
- package/dist/client/assets/{ProfilePreview-DQX6_xc3.js → ProfilePreview-CHbT0U9a.js} +1 -1
- package/dist/client/assets/{SavedTimelineView-BhSiVyR1.js → SavedTimelineView-mGXE9EYJ.js} +1 -1
- package/dist/client/assets/{SmartTimestamp-BR0-9se2.js → SmartTimestamp-DG8XJKUz.js} +1 -1
- package/dist/client/assets/TimelineRouteFrame-BmMC7qLj.js +1 -0
- package/dist/client/assets/blocks-CawnB0mN.js +1 -0
- package/dist/client/assets/{bookmarks-LbYOsoKf.js → bookmarks-DZIMTKEp.js} +1 -1
- package/dist/client/assets/client-stream-contracts-DcKcyZhg.js +5 -0
- package/dist/client/assets/{data-sources-euTr6T_L.js → data-sources-BJUT2qaO.js} +1 -1
- package/dist/client/assets/{discuss-BWKu_p7B.js → discuss-DMPJVaRk.js} +1 -1
- package/dist/client/assets/dms-BfPFG4Qr.js +1 -0
- package/dist/client/assets/{inbox-BybSI5Vf.js → inbox-DmeYJ7tt.js} +1 -1
- package/dist/client/assets/{index-DZVisyna.js → index-Bqmo4MbK.js} +6 -6
- package/dist/client/assets/likes-BxVL-j_C.js +1 -0
- package/dist/client/assets/{links-CIwqK_dX.js → links-BeA1Wt8F.js} +1 -1
- package/dist/client/assets/{mapbox-B4zqX7r0.js → mapbox-BTSMNSry.js} +2 -2
- package/dist/client/assets/{mapbox-gl-DYNcLcll.js → mapbox-gl-CPemBIlO.js} +1 -1
- package/dist/client/assets/{mentions-BoVVAIT7.js → mentions-DBrY-UPg.js} +1 -1
- package/dist/client/assets/{network-map-zRysIY_b.js → network-map-MqbE164r.js} +2 -2
- package/dist/client/assets/{profile-analyze-C_yDeX6-.js → profile-analyze-DEA75Reh.js} +1 -1
- package/dist/client/assets/{profile-hydration-client-YEtJsNTv.js → profile-hydration-client-BNe0sUmK.js} +1 -1
- package/dist/client/assets/{profiles._handle-DdIVPqU-.js → profiles._handle-K34ofz9U.js} +1 -1
- package/dist/client/assets/{query-client-fYu2rzRC.js → query-client-DUP1ZzP7.js} +1 -1
- package/dist/client/assets/{rate-limits-BWPmPOSh.js → rate-limits-B8Wmpt9r.js} +1 -1
- package/dist/client/assets/{routes-CiLuYpZr.js → routes-Bl-Lw2hN.js} +1 -1
- package/dist/client/assets/schemas-CaALBBpc.js +64 -0
- package/dist/client/assets/styles-zihNNCGg.css +2 -0
- package/dist/client/assets/today-DFvZ0Ys5.js +1 -0
- package/dist/client/assets/{useTimelineRouteData-BaO3Rna1.js → useTimelineRouteData-Cpf-Jqsx.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-L7bs6IHH.js → ProfileAnalysisStream-CLjrYzEx.js} +2 -2
- package/dist/server/assets/{ProfilePreview-Cg-AOHBE.js → ProfilePreview-Bq-DSUM2.js} +2 -2
- package/dist/server/assets/{SavedTimelineView-BtCq1dHk.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-CjMIh63G.js → TimelineRouteFrame-Bg3e7X_N.js} +5 -3
- package/dist/server/assets/{TweetRichText-DDywESRF.js → TweetRichText-wzUAtbPS.js} +2 -2
- package/dist/server/assets/{_tanstack-start-manifest_v-aLWUdbAc.js → _tanstack-start-manifest_v-CVfs07C8.js} +24 -24
- 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-D_YkmCK3.js → bookmarks-CPFq3JVX.js} +1 -1
- package/dist/server/assets/{client-stream-contracts-Bq6kMmhh.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-DAq6ehL9.js → discuss-BB8wFy4L.js} +3 -3
- package/dist/server/assets/{discuss-C1QMBBSk.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/{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-BMm2JWYI.js → likes-CDAjYyTm.js} +1 -1
- package/dist/server/assets/{links-dqXb35YM.js → links-DU-TIHtg.js} +6 -6
- package/dist/server/assets/{links-Da4tjWtI.js → links-sQ3q2jvO.js} +6 -6
- package/dist/server/assets/{mentions-CEyEcOFw.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-DH1TSBkT.js → profile-analyze-BmDeTif5.js} +1 -1
- package/dist/server/assets/{profile-analyze-CGCj4VFd.js → profile-analyze-CUfXmXEH.js} +3 -3
- package/dist/server/assets/{profiles._handle-B5V-huoo.js → profiles._handle-8snagiR1.js} +4 -4
- package/dist/server/assets/{profiles._handle-CJ6rgzt8.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-C4cz9ypd.js → router-CAvZOdfA.js} +437 -108
- package/dist/server/assets/{routes-B9aikOdB.js → routes-D9226vKz.js} +1 -1
- package/dist/server/assets/{today-JkxZXdBt.js → today-BmvIMJzI.js} +3 -3
- package/dist/server/assets/{today-B_TA16QG.js → today-DKWphGrC.js} +112 -57
- package/dist/server/assets/{ui-qbmcMSSX.js → ui-rDHeS-cc.js} +2 -1
- package/dist/server/assets/{useTimelineRouteData-BLXvMGb2.js → useTimelineRouteData-BmDbGvQ8.js} +7 -7
- package/dist/server/server.js +2 -2
- package/package.json +13 -13
- package/dist/client/assets/TimelineRouteFrame-9NDLnXzH.js +0 -1
- package/dist/client/assets/blocks-CFp_njvG.js +0 -1
- package/dist/client/assets/client-stream-contracts-sR4xMQxY.js +0 -5
- package/dist/client/assets/dms-SLXMtC4v.js +0 -1
- package/dist/client/assets/likes-dyh9p8y_.js +0 -1
- package/dist/client/assets/schemas-I9r6F6ya.js +0 -64
- package/dist/client/assets/styles-B8s3tt3r.css +0 -2
- package/dist/client/assets/today-BoO64YoY.js +0 -1
- package/dist/server/assets/SyncNowButton-BOn_IBXS.js +0 -76
|
@@ -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-zihNNCGg.css";
|
|
523
525
|
//#endregion
|
|
524
526
|
//#region src/routes/__root.tsx
|
|
525
527
|
var Route$24 = createRootRoute({
|
|
@@ -572,27 +574,27 @@ 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
|
|
@@ -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);
|
|
@@ -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
|
|
@@ -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) {
|
|
@@ -5076,9 +5338,11 @@ function parseBirdWhoami(stdout) {
|
|
|
5076
5338
|
if (/[A-Za-z@]/.test(line)) return;
|
|
5077
5339
|
return line.match(/^\D*(\d{2,})\D*$/)?.[1];
|
|
5078
5340
|
}).find((value) => Boolean(value));
|
|
5341
|
+
const name = stdout.match(/@[A-Za-z0-9_]{1,15}\s*\(([^)]+)\)/)?.[1]?.trim();
|
|
5079
5342
|
return {
|
|
5080
5343
|
username: usernameMatch[1],
|
|
5081
|
-
...id ? { id } : {}
|
|
5344
|
+
...id ? { id } : {},
|
|
5345
|
+
...name ? { name } : {}
|
|
5082
5346
|
};
|
|
5083
5347
|
}
|
|
5084
5348
|
function getAuthenticatedBirdAccountEffect() {
|
|
@@ -7747,6 +8011,12 @@ function buildMediaJsonFromIncludes(tweet, media = []) {
|
|
|
7747
8011
|
function getReferencedTweetId(tweet, type) {
|
|
7748
8012
|
return tweet.referenced_tweets?.find((item) => item.type === type)?.id ?? null;
|
|
7749
8013
|
}
|
|
8014
|
+
function toCanonicalTweets(payload) {
|
|
8015
|
+
const tweetsById = /* @__PURE__ */ new Map();
|
|
8016
|
+
for (const tweet of payload.includes?.tweets ?? []) tweetsById.set(tweet.id, tweet);
|
|
8017
|
+
for (const tweet of payload.data) tweetsById.set(tweet.id, tweet);
|
|
8018
|
+
return tweetsById.values();
|
|
8019
|
+
}
|
|
7750
8020
|
function replaceTweetFts$1(db, tweetId, text) {
|
|
7751
8021
|
db.prepare("delete from tweets_fts where tweet_id = ?").run(tweetId);
|
|
7752
8022
|
db.prepare("insert into tweets_fts (tweet_id, text) values (?, ?)").run(tweetId, text);
|
|
@@ -7764,7 +8034,7 @@ function ingestTweetPayload(db, { accountId, payload, source, edgeKind, collecti
|
|
|
7764
8034
|
created_at = excluded.created_at,
|
|
7765
8035
|
is_replied = max(tweets.is_replied, excluded.is_replied),
|
|
7766
8036
|
reply_to_id = coalesce(tweets.reply_to_id, excluded.reply_to_id),
|
|
7767
|
-
like_count = excluded.like_count,
|
|
8037
|
+
like_count = case when ? then tweets.like_count else excluded.like_count end,
|
|
7768
8038
|
media_count = max(tweets.media_count, excluded.media_count),
|
|
7769
8039
|
entities_json = excluded.entities_json,
|
|
7770
8040
|
media_json = case
|
|
@@ -7785,13 +8055,16 @@ function ingestTweetPayload(db, { accountId, payload, source, edgeKind, collecti
|
|
|
7785
8055
|
const tweetIds = [];
|
|
7786
8056
|
db.transaction(() => {
|
|
7787
8057
|
const observedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7788
|
-
|
|
8058
|
+
const primaryTweetIds = new Set(payload.data.map((tweet) => tweet.id));
|
|
8059
|
+
for (const tweet of toCanonicalTweets(payload)) {
|
|
8060
|
+
const isPrimaryTweet = primaryTweetIds.has(tweet.id);
|
|
7789
8061
|
const author = usersById.get(tweet.author_id);
|
|
7790
8062
|
const profile = author ? upsertProfileFromXUser(db, author) : ensureStubProfileForXUser(db, tweet.author_id);
|
|
7791
8063
|
const replyToId = getReferencedTweetId(tweet, "replied_to");
|
|
7792
8064
|
const quotedTweetId = getReferencedTweetId(tweet, "quoted");
|
|
7793
|
-
|
|
7794
|
-
|
|
8065
|
+
const shouldMarkReplied = isPrimaryTweet && markRepliesAsReplied && Boolean(replyToId);
|
|
8066
|
+
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);
|
|
8067
|
+
if (edgeKind && isPrimaryTweet) upsertTweetAccountEdge(db, {
|
|
7795
8068
|
accountId,
|
|
7796
8069
|
tweetId: tweet.id,
|
|
7797
8070
|
kind: edgeKind,
|
|
@@ -7799,9 +8072,9 @@ function ingestTweetPayload(db, { accountId, payload, source, edgeKind, collecti
|
|
|
7799
8072
|
seenAt: observedAt,
|
|
7800
8073
|
rawJson: JSON.stringify(tweet)
|
|
7801
8074
|
});
|
|
7802
|
-
upsertCollection?.run(accountId, tweet.id, collectionKind, source, JSON.stringify(tweet), observedAt);
|
|
8075
|
+
if (isPrimaryTweet) upsertCollection?.run(accountId, tweet.id, collectionKind, source, JSON.stringify(tweet), observedAt);
|
|
7803
8076
|
replaceTweetFts$1(db, tweet.id, tweet.text);
|
|
7804
|
-
tweetIds.push(tweet.id);
|
|
8077
|
+
if (isPrimaryTweet) tweetIds.push(tweet.id);
|
|
7805
8078
|
}
|
|
7806
8079
|
})();
|
|
7807
8080
|
return tweetIds;
|
|
@@ -8539,7 +8812,7 @@ function getTimelineQualityReason(row, lowQualityThreshold) {
|
|
|
8539
8812
|
return "keep:long-text";
|
|
8540
8813
|
}
|
|
8541
8814
|
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 }) {
|
|
8815
|
+
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
8816
|
const db = getReadDb();
|
|
8544
8817
|
const kind = resource === "mentions" ? "mention" : resource;
|
|
8545
8818
|
const params = [];
|
|
@@ -8557,7 +8830,7 @@ function listTimelineItems({ resource, account, search, replyFilter = "all", sin
|
|
|
8557
8830
|
let join = "";
|
|
8558
8831
|
let where = "where e.kind = ?";
|
|
8559
8832
|
let searchSnippetSelect = "";
|
|
8560
|
-
const canUseRecentEdgeWindow = !likedOnly && !bookmarkedOnly && !account && !search?.trim() && replyFilter === "all" && !since?.trim() && !until?.trim() && includeReplies && qualityFilter === "all";
|
|
8833
|
+
const canUseRecentEdgeWindow = !likedOnly && !bookmarkedOnly && !listId && !account && !search?.trim() && replyFilter === "all" && !since?.trim() && !until?.trim() && includeReplies && qualityFilter === "all";
|
|
8561
8834
|
if (likedOnly || bookmarkedOnly) {
|
|
8562
8835
|
if (likedOnly && bookmarkedOnly) timelineEdgesCte = `
|
|
8563
8836
|
with timeline_edges as (
|
|
@@ -8637,6 +8910,20 @@ function listTimelineItems({ resource, account, search, replyFilter = "all", sin
|
|
|
8637
8910
|
where += " and t.created_at < ?";
|
|
8638
8911
|
params.push(until.trim());
|
|
8639
8912
|
}
|
|
8913
|
+
if (Boolean(listId) !== Boolean(listAccountId)) throw new Error("List filtering requires both listId and listAccountId");
|
|
8914
|
+
if (listId && listAccountId) {
|
|
8915
|
+
where += `
|
|
8916
|
+
and exists (
|
|
8917
|
+
select 1
|
|
8918
|
+
from x_list_members list_member
|
|
8919
|
+
where list_member.account_id = ?
|
|
8920
|
+
and list_member.list_id = ?
|
|
8921
|
+
and list_member.profile_id = t.author_profile_id
|
|
8922
|
+
and list_member.current = 1
|
|
8923
|
+
)
|
|
8924
|
+
`;
|
|
8925
|
+
params.push(listAccountId, listId);
|
|
8926
|
+
}
|
|
8640
8927
|
const ftsSearch = search?.trim() ? toFtsSearchQuery(search) : "";
|
|
8641
8928
|
if (ftsSearch) {
|
|
8642
8929
|
join += " join tweets_fts on tweets_fts.tweet_id = t.id ";
|
|
@@ -9654,6 +9941,7 @@ function reachedStartTimeBoundary(payload, startTimeMs) {
|
|
|
9654
9941
|
function mergeTimelinePayloads(payloads, limit) {
|
|
9655
9942
|
const data = [];
|
|
9656
9943
|
const usersById = /* @__PURE__ */ new Map();
|
|
9944
|
+
const tweetsById = /* @__PURE__ */ new Map();
|
|
9657
9945
|
const mediaByKey = /* @__PURE__ */ new Map();
|
|
9658
9946
|
let meta;
|
|
9659
9947
|
for (const payload of payloads) {
|
|
@@ -9664,6 +9952,7 @@ function mergeTimelinePayloads(payloads, limit) {
|
|
|
9664
9952
|
if (data.length >= limit) break;
|
|
9665
9953
|
}
|
|
9666
9954
|
for (const user of payload.includes?.users ?? []) usersById.set(user.id, user);
|
|
9955
|
+
for (const tweet of payload.includes?.tweets ?? []) tweetsById.set(tweet.id, tweet);
|
|
9667
9956
|
for (const media of payload.includes?.media ?? []) mediaByKey.set(media.media_key, media);
|
|
9668
9957
|
if (data.length >= limit) break;
|
|
9669
9958
|
}
|
|
@@ -9671,6 +9960,7 @@ function mergeTimelinePayloads(payloads, limit) {
|
|
|
9671
9960
|
data,
|
|
9672
9961
|
includes: {
|
|
9673
9962
|
users: [...usersById.values()],
|
|
9963
|
+
tweets: [...tweetsById.values()],
|
|
9674
9964
|
media: [...mediaByKey.values()]
|
|
9675
9965
|
},
|
|
9676
9966
|
meta
|
|
@@ -10377,6 +10667,26 @@ var DEFAULT_DELIMITER_HOLD = 8;
|
|
|
10377
10667
|
function toError$13(error) {
|
|
10378
10668
|
return error instanceof Error ? error : new Error(String(error));
|
|
10379
10669
|
}
|
|
10670
|
+
var DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
10671
|
+
/**
|
|
10672
|
+
* Resolve the OpenAI-compatible API base URL. Point this at Ollama
|
|
10673
|
+
* (`http://localhost:11434/v1`) or any other OpenAI-compatible server via
|
|
10674
|
+
* the Birdclaw-specific `BIRDCLAW_OPENAI_BASE_URL`. The previously shipped
|
|
10675
|
+
* `OPENAI_BASE_URL` setting remains a lower-priority compatibility fallback.
|
|
10676
|
+
* A trailing slash is trimmed so callers can safely append `/responses` etc.
|
|
10677
|
+
*/
|
|
10678
|
+
function resolveOpenAIBaseUrl(getEnv) {
|
|
10679
|
+
return ((getEnv("BIRDCLAW_OPENAI_BASE_URL") || getEnv("OPENAI_BASE_URL"))?.trim() || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");
|
|
10680
|
+
}
|
|
10681
|
+
/**
|
|
10682
|
+
* Emit an OpenAI-transport debug line to stderr when `BIRDCLAW_DEBUG` is set.
|
|
10683
|
+
* Gated so normal runs stay quiet; enable with `BIRDCLAW_DEBUG=1`.
|
|
10684
|
+
*/
|
|
10685
|
+
function debugLog(getEnv, message) {
|
|
10686
|
+
if (!getEnv("BIRDCLAW_DEBUG")) return;
|
|
10687
|
+
if (typeof process === "undefined") return;
|
|
10688
|
+
process.stderr.write(`[birdclaw:openai] ${message}\n`);
|
|
10689
|
+
}
|
|
10380
10690
|
function createOpenAIStreamState() {
|
|
10381
10691
|
return {
|
|
10382
10692
|
eventBuffer: "",
|
|
@@ -10474,7 +10784,9 @@ function requestOpenAIResponseEffect({ body, signal, runtime = defaultRuntimeSer
|
|
|
10474
10784
|
return Effect.gen(function* () {
|
|
10475
10785
|
const apiKey = runtime.env("OPENAI_API_KEY");
|
|
10476
10786
|
if (!apiKey) return yield* Effect.fail(/* @__PURE__ */ new Error("OPENAI_API_KEY is not set"));
|
|
10477
|
-
const
|
|
10787
|
+
const url = `${resolveOpenAIBaseUrl(runtime.env)}/responses`;
|
|
10788
|
+
debugLog(runtime.env, `POST ${url}`);
|
|
10789
|
+
const response = yield* tryPromise(() => runtime.fetch(url, {
|
|
10478
10790
|
method: "POST",
|
|
10479
10791
|
signal,
|
|
10480
10792
|
headers: {
|
|
@@ -10482,11 +10794,13 @@ function requestOpenAIResponseEffect({ body, signal, runtime = defaultRuntimeSer
|
|
|
10482
10794
|
"content-type": "application/json"
|
|
10483
10795
|
},
|
|
10484
10796
|
body: JSON.stringify(body)
|
|
10485
|
-
})).pipe(Effect.mapError(toError$13));
|
|
10797
|
+
})).pipe(Effect.mapError(toError$13), Effect.tapError((error) => Effect.sync(() => debugLog(runtime.env, `network error for ${url}: ${error.message}`))));
|
|
10486
10798
|
if (!response.ok) {
|
|
10487
10799
|
const text = yield* tryPromise(() => response.text()).pipe(Effect.mapError(toError$13));
|
|
10800
|
+
debugLog(runtime.env, `${url} -> ${String(response.status)} ${text.slice(0, 400)}`);
|
|
10488
10801
|
return yield* Effect.fail(/* @__PURE__ */ new Error(`OpenAI request failed: ${String(response.status)} ${text.slice(0, 400)}`));
|
|
10489
10802
|
}
|
|
10803
|
+
debugLog(runtime.env, `${url} -> ${String(response.status)} OK`);
|
|
10490
10804
|
return response;
|
|
10491
10805
|
});
|
|
10492
10806
|
}
|
|
@@ -11257,6 +11571,8 @@ function compactTweet$1(source, item) {
|
|
|
11257
11571
|
authorProfile: item.author,
|
|
11258
11572
|
createdAt: item.createdAt,
|
|
11259
11573
|
text: item.text,
|
|
11574
|
+
entities: item.entities,
|
|
11575
|
+
media: item.media,
|
|
11260
11576
|
likeCount: item.likeCount,
|
|
11261
11577
|
liked: item.liked,
|
|
11262
11578
|
bookmarked: item.bookmarked,
|
|
@@ -11291,7 +11607,9 @@ function collectLiveSearchTweets(options) {
|
|
|
11291
11607
|
t.created_at,
|
|
11292
11608
|
t.is_replied,
|
|
11293
11609
|
t.like_count,
|
|
11294
|
-
|
|
11610
|
+
t.media_count,
|
|
11611
|
+
t.entities_json,
|
|
11612
|
+
t.media_json,
|
|
11295
11613
|
case
|
|
11296
11614
|
when exists (
|
|
11297
11615
|
select 1 from tweet_collections collection
|
|
@@ -11341,6 +11659,8 @@ function collectLiveSearchTweets(options) {
|
|
|
11341
11659
|
},
|
|
11342
11660
|
createdAt: String(row.created_at),
|
|
11343
11661
|
text: String(row.text),
|
|
11662
|
+
entities: parseJsonField$1(row.entities_json, {}),
|
|
11663
|
+
media: parseJsonField$1(row.media_json, []),
|
|
11344
11664
|
likeCount: Number(row.like_count),
|
|
11345
11665
|
liked: Boolean(row.liked),
|
|
11346
11666
|
bookmarked: Boolean(row.bookmarked),
|
|
@@ -13487,6 +13807,7 @@ function compactTweet(source, item) {
|
|
|
13487
13807
|
createdAt: item.createdAt,
|
|
13488
13808
|
text: item.text,
|
|
13489
13809
|
entities: item.entities,
|
|
13810
|
+
media: item.media,
|
|
13490
13811
|
likeCount: item.likeCount,
|
|
13491
13812
|
liked: item.liked,
|
|
13492
13813
|
bookmarked: item.bookmarked,
|
|
@@ -13506,6 +13827,7 @@ function compactEmbeddedTweet(item) {
|
|
|
13506
13827
|
createdAt: item.createdAt,
|
|
13507
13828
|
text: item.text,
|
|
13508
13829
|
entities: item.entities,
|
|
13830
|
+
media: item.media,
|
|
13509
13831
|
likeCount: item.likeCount ?? 0,
|
|
13510
13832
|
liked: Boolean(item.liked),
|
|
13511
13833
|
bookmarked: Boolean(item.bookmarked),
|
|
@@ -15221,6 +15543,7 @@ var Route$7 = createFileRoute("/api/link-insights")({ server: { handlers: { GET:
|
|
|
15221
15543
|
})) } } });
|
|
15222
15544
|
//#endregion
|
|
15223
15545
|
//#region src/lib/openai.ts
|
|
15546
|
+
var getEnv = (name) => process.env[name];
|
|
15224
15547
|
function clampScore(value) {
|
|
15225
15548
|
return Math.max(0, Math.min(100, Math.round(value)));
|
|
15226
15549
|
}
|
|
@@ -15238,7 +15561,9 @@ function scoreInboxItemWithOpenAIEffect(input) {
|
|
|
15238
15561
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
15239
15562
|
if (!apiKey) return yield* Effect.fail(/* @__PURE__ */ new Error("OPENAI_API_KEY is not set"));
|
|
15240
15563
|
const model = process.env.BIRDCLAW_OPENAI_MODEL || "gpt-5.2";
|
|
15241
|
-
const
|
|
15564
|
+
const url = `${resolveOpenAIBaseUrl(getEnv)}/chat/completions`;
|
|
15565
|
+
debugLog(getEnv, `POST ${url} (model=${model})`);
|
|
15566
|
+
const response = yield* tryPromise(() => fetch(url, {
|
|
15242
15567
|
method: "POST",
|
|
15243
15568
|
headers: {
|
|
15244
15569
|
authorization: `Bearer ${apiKey}`,
|
|
@@ -15255,8 +15580,12 @@ function scoreInboxItemWithOpenAIEffect(input) {
|
|
|
15255
15580
|
content: JSON.stringify(input)
|
|
15256
15581
|
}]
|
|
15257
15582
|
})
|
|
15258
|
-
})).pipe(Effect.mapError(toError$7));
|
|
15259
|
-
if (!response.ok)
|
|
15583
|
+
})).pipe(Effect.mapError(toError$7), Effect.tapError((error) => Effect.sync(() => debugLog(getEnv, `network error for ${url}: ${error.message}`))));
|
|
15584
|
+
if (!response.ok) {
|
|
15585
|
+
const text = yield* tryPromise(() => response.text()).pipe(Effect.mapError(toError$7));
|
|
15586
|
+
debugLog(getEnv, `${url} -> ${response.status} ${text.slice(0, 400)}`);
|
|
15587
|
+
return yield* Effect.fail(/* @__PURE__ */ new Error(`OpenAI request failed: ${response.status} ${text.slice(0, 400)}`));
|
|
15588
|
+
}
|
|
15260
15589
|
const content = (yield* tryPromise(() => response.json()).pipe(Effect.mapError(toError$7))).choices?.[0]?.message?.content;
|
|
15261
15590
|
if (!content) return yield* Effect.fail(/* @__PURE__ */ new Error("OpenAI returned no content"));
|
|
15262
15591
|
const parsed = yield* trySync$6(() => JSON.parse(content));
|