birdclaw 0.4.1 → 0.5.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 +52 -0
- package/README.md +113 -7
- package/bin/birdclaw.mjs +50 -11
- package/package.json +30 -28
- package/playwright.config.ts +1 -0
- package/public/birdclaw-mark.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +2 -2
- package/scripts/browser-perf.mjs +399 -0
- package/scripts/build-docs-site.mjs +940 -0
- package/scripts/docs-site-assets.mjs +311 -0
- package/scripts/run-vitest.mjs +21 -0
- package/scripts/sanitize-node-options.mjs +23 -0
- package/scripts/start-test-server.mjs +29 -0
- package/src/cli.ts +496 -19
- package/src/components/AppNav.tsx +66 -29
- package/src/components/AvatarChip.tsx +10 -5
- package/src/components/BrandMark.tsx +67 -0
- package/src/components/ConversationThread.tsx +126 -0
- package/src/components/DmWorkspace.tsx +118 -105
- package/src/components/EmbeddedTweetCard.tsx +20 -14
- package/src/components/FeedState.tsx +147 -0
- package/src/components/InboxCard.tsx +104 -90
- package/src/components/LinkPreviewCard.tsx +270 -0
- package/src/components/ProfilePreview.tsx +8 -3
- package/src/components/SavedTimelineView.tsx +89 -71
- package/src/components/SyncNowButton.tsx +105 -0
- package/src/components/ThemeSlider.tsx +10 -59
- package/src/components/TimelineCard.tsx +326 -86
- package/src/components/TimelineRouteFrame.tsx +156 -0
- package/src/components/TweetMediaGrid.tsx +120 -23
- package/src/components/TweetRichText.tsx +19 -4
- package/src/components/useTimelineRouteData.ts +137 -0
- package/src/lib/api-client.ts +229 -0
- package/src/lib/archive-finder.ts +24 -20
- package/src/lib/archive-import.ts +1582 -67
- package/src/lib/authored-live.ts +1074 -0
- package/src/lib/backup.ts +316 -14
- package/src/lib/bird-actions.ts +1 -10
- package/src/lib/bird-command.ts +57 -0
- package/src/lib/bird.ts +89 -5
- package/src/lib/config.ts +1 -1
- package/src/lib/conversation-surface.ts +174 -0
- package/src/lib/db.ts +193 -4
- package/src/lib/follow-graph.ts +1053 -0
- package/src/lib/link-index.ts +11 -98
- package/src/lib/link-insights.ts +834 -0
- package/src/lib/link-preview-metadata.ts +334 -0
- package/src/lib/media-fetch.ts +787 -0
- package/src/lib/media-includes.ts +165 -0
- package/src/lib/mention-threads-live.ts +535 -43
- package/src/lib/mentions-live.ts +623 -19
- package/src/lib/moderation-target.ts +6 -0
- package/src/lib/profile-hydration.ts +1 -1
- package/src/lib/profile-resolver.ts +115 -1
- package/src/lib/queries.ts +326 -35
- package/src/lib/seed.ts +127 -8
- package/src/lib/timeline-collections-live.ts +145 -22
- package/src/lib/timeline-live.ts +10 -15
- package/src/lib/tweet-account-edges.ts +9 -2
- package/src/lib/tweet-render.ts +97 -0
- package/src/lib/types.ts +185 -2
- package/src/lib/ui.ts +383 -147
- package/src/lib/url-expansion-store.ts +110 -0
- package/src/lib/url-expansion.ts +20 -3
- package/src/lib/web-sync.ts +443 -0
- package/src/lib/x-profile.ts +7 -2
- package/src/lib/xurl.ts +296 -12
- package/src/routeTree.gen.ts +126 -0
- package/src/routes/__root.tsx +7 -3
- package/src/routes/api/conversation.tsx +34 -0
- package/src/routes/api/link-insights.tsx +79 -0
- package/src/routes/api/link-preview.tsx +43 -0
- package/src/routes/api/profile-hydrate.tsx +51 -0
- package/src/routes/api/sync.tsx +59 -0
- package/src/routes/blocks.tsx +111 -86
- package/src/routes/dms.tsx +172 -87
- package/src/routes/inbox.tsx +98 -86
- package/src/routes/index.tsx +22 -115
- package/src/routes/links.tsx +928 -0
- package/src/routes/mentions.tsx +22 -115
- package/src/styles.css +169 -43
- package/vite.config.ts +8 -0
package/src/lib/link-index.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { getNativeDb } from "./db";
|
|
2
2
|
import type { Database } from "./sqlite";
|
|
3
|
+
import {
|
|
4
|
+
normalizeUrlExpansionForIndex,
|
|
5
|
+
upsertUrlExpansion,
|
|
6
|
+
} from "./url-expansion-store";
|
|
3
7
|
import {
|
|
4
8
|
expandUrls,
|
|
5
9
|
extractUrls,
|
|
@@ -95,101 +99,6 @@ function isIndexedUrl(url: string, includeAllUrls: boolean) {
|
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
function getTweetTarget(url: string) {
|
|
99
|
-
try {
|
|
100
|
-
const parsed = new URL(url);
|
|
101
|
-
const host = parsed.hostname.toLowerCase();
|
|
102
|
-
if (
|
|
103
|
-
host !== "x.com" &&
|
|
104
|
-
host !== "twitter.com" &&
|
|
105
|
-
host !== "mobile.twitter.com" &&
|
|
106
|
-
host !== "www.x.com" &&
|
|
107
|
-
host !== "www.twitter.com"
|
|
108
|
-
) {
|
|
109
|
-
return {};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
113
|
-
const statusIndex = parts.findIndex(
|
|
114
|
-
(part) => part === "status" || part === "statuses",
|
|
115
|
-
);
|
|
116
|
-
if (statusIndex === -1 || !parts[statusIndex + 1]) {
|
|
117
|
-
return {};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const tweetId = parts[statusIndex + 1]?.match(/^\d+/)?.[0];
|
|
121
|
-
const handle =
|
|
122
|
-
parts[statusIndex - 1] && parts[statusIndex - 1] !== "i"
|
|
123
|
-
? parts[statusIndex - 1]
|
|
124
|
-
: undefined;
|
|
125
|
-
return {
|
|
126
|
-
expandedTweetId: tweetId,
|
|
127
|
-
expandedHandle: handle,
|
|
128
|
-
};
|
|
129
|
-
} catch {
|
|
130
|
-
return {};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function normalizeExpansion(item: {
|
|
135
|
-
url: string;
|
|
136
|
-
expandedUrl: string;
|
|
137
|
-
finalUrl: string;
|
|
138
|
-
status: "hit" | "miss" | "error";
|
|
139
|
-
title?: string;
|
|
140
|
-
description?: string | null;
|
|
141
|
-
error?: string;
|
|
142
|
-
source: string;
|
|
143
|
-
updatedAt: string;
|
|
144
|
-
}): LinkIndexItem {
|
|
145
|
-
const target = getTweetTarget(item.finalUrl);
|
|
146
|
-
return {
|
|
147
|
-
shortUrl: item.url,
|
|
148
|
-
expandedUrl: item.expandedUrl,
|
|
149
|
-
finalUrl: item.finalUrl,
|
|
150
|
-
status: item.status,
|
|
151
|
-
expandedTweetId: target.expandedTweetId ?? null,
|
|
152
|
-
expandedHandle: target.expandedHandle ?? null,
|
|
153
|
-
title: item.title ?? null,
|
|
154
|
-
description: item.description ?? null,
|
|
155
|
-
error: item.error ?? null,
|
|
156
|
-
source: item.source,
|
|
157
|
-
updatedAt: item.updatedAt,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function upsertExpansion(db: Database, item: LinkIndexItem) {
|
|
162
|
-
db.prepare(`
|
|
163
|
-
insert into url_expansions (
|
|
164
|
-
short_url, expanded_url, final_url, status, expanded_tweet_id,
|
|
165
|
-
expanded_handle, title, description, error, source, updated_at
|
|
166
|
-
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
167
|
-
on conflict(short_url) do update set
|
|
168
|
-
expanded_url = excluded.expanded_url,
|
|
169
|
-
final_url = excluded.final_url,
|
|
170
|
-
status = excluded.status,
|
|
171
|
-
expanded_tweet_id = excluded.expanded_tweet_id,
|
|
172
|
-
expanded_handle = excluded.expanded_handle,
|
|
173
|
-
title = excluded.title,
|
|
174
|
-
description = excluded.description,
|
|
175
|
-
error = excluded.error,
|
|
176
|
-
source = excluded.source,
|
|
177
|
-
updated_at = excluded.updated_at
|
|
178
|
-
`).run(
|
|
179
|
-
item.shortUrl,
|
|
180
|
-
item.expandedUrl,
|
|
181
|
-
item.finalUrl,
|
|
182
|
-
item.status,
|
|
183
|
-
item.expandedTweetId,
|
|
184
|
-
item.expandedHandle,
|
|
185
|
-
item.title,
|
|
186
|
-
item.description,
|
|
187
|
-
item.error,
|
|
188
|
-
item.source,
|
|
189
|
-
item.updatedAt,
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
102
|
function toTweetEntityUrls(entitiesJson: string): SourceUrl[] {
|
|
194
103
|
const entities = parseJsonField<TweetEntities>(entitiesJson, {});
|
|
195
104
|
const urls = Array.isArray(entities.urls) ? entities.urls : [];
|
|
@@ -315,9 +224,9 @@ function rebuildOccurrences(
|
|
|
315
224
|
);
|
|
316
225
|
occurrences++;
|
|
317
226
|
if (entry.expandedUrl) {
|
|
318
|
-
|
|
227
|
+
upsertUrlExpansion(
|
|
319
228
|
db,
|
|
320
|
-
|
|
229
|
+
normalizeUrlExpansionForIndex({
|
|
321
230
|
url: entry.url,
|
|
322
231
|
expandedUrl: entry.expandedUrl,
|
|
323
232
|
finalUrl: entry.expandedUrl,
|
|
@@ -379,7 +288,7 @@ async function expandWithConcurrency(
|
|
|
379
288
|
if (result.status === "error") {
|
|
380
289
|
counts.errors++;
|
|
381
290
|
}
|
|
382
|
-
|
|
291
|
+
upsertUrlExpansion(db, normalizeUrlExpansionForIndex(result));
|
|
383
292
|
}
|
|
384
293
|
}
|
|
385
294
|
|
|
@@ -532,6 +441,8 @@ function toLinkSearchItem(row: Record<string, unknown>): LinkSearchItem {
|
|
|
532
441
|
expandedHandle: getString(row.expanded_handle) ?? null,
|
|
533
442
|
title: getString(row.title) ?? null,
|
|
534
443
|
description: getString(row.description) ?? null,
|
|
444
|
+
imageUrl: getString(row.image_url) ?? null,
|
|
445
|
+
siteName: getString(row.site_name) ?? null,
|
|
535
446
|
error: getString(row.error) ?? null,
|
|
536
447
|
source: String(row.expansion_source),
|
|
537
448
|
updatedAt: String(row.updated_at),
|
|
@@ -630,6 +541,8 @@ export function searchLinks(query: string, options: LinkSearchOptions = {}) {
|
|
|
630
541
|
e.expanded_handle,
|
|
631
542
|
e.title,
|
|
632
543
|
e.description,
|
|
544
|
+
e.image_url,
|
|
545
|
+
e.site_name,
|
|
633
546
|
e.error,
|
|
634
547
|
e.source as expansion_source,
|
|
635
548
|
e.updated_at,
|